dodo  0.0.1
A C++ library to create containerized Linux services
kvstore.hpp
Go to the documentation of this file.
1 /*
2  * This file is part of the dodo library (https://github.com/jmspit/dodo).
3  * Copyright (c) 2019 Jan-Marten Spit.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, version 3.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /**
19  * @file kvstore.hpp
20  * Defines the KVStore class.
21  */
22 
23 #ifndef dodo_kvstore_hpp
24 #define dodo_kvstore_hpp
25 
26 #include <filesystem>
27 #include <list>
28 #include <vector>
29 #include <sqlite3.h>
30 #include <common/bytes.hpp>
31 
32 /**
33  * Persistent storage structures.
34  */
35 namespace dodo::persist {
36 
37  /**
38  * A persistent, multi-threaded key-value store backed by sqlite3 database (file). If each thread uses its
39  * own KVStore object, no explicit synchronization is required between the threads. Cluster filesystems are
40  * not supported - two processes can only write to a single SQLite file if the processes run on the same host.
41  *
42  * The KVStore is backed by the kvstore table. In SQLite, values in the same column can have different data types, so
43  * the table can store values of any of the supported data types:
44  *
45  * | DataType | C++ type | SQLite type |
46  * |-------------------|----------|-------------|
47  * | dtInteger | int64_t | SQLITE_INTEGER |
48  * | dtFloat | double | SQLITE_FLOAT |
49  * | dtText | std::string | SQLITE3_TEXT |
50  * | dtBlob | common::Bytes | SQLITE_BLOB |
51  * | dtUnknown | used to indicate the unknown dataType of a non-existing key | |
52  *
53  * In typical use the code is aware of the datatype of a key, so typical use would be
54  *
55  * @code
56  * persist::KVStore store("mystore.db");
57  * store.insertKey( "pi", 3.14 ); // insert as a double value
58  * double pi = store.asDouble( "pi" ); // retrieve as a double value as we know it is a double.
59  * @endcode
60  *
61  * Moreover, SQLite will convert (CAST) between types where possible, but revert to defaults when it can't. For example,
62  *
63  * @code
64  * store.insertKey( "key", "value" );
65  * cout << store.asInteger( "key" ) << endl;
66  * @endcode
67  * will output 0 as the string "value" does not convert to an integer. Use getMetaData to check if a key is of the expected DataType.
68  *
69  * It is also possible to store dodo::common::Bytes (dtBlob / SQLITE_BLOB) types.
70  *
71  * The SQLite database is initailized in WAL mode for performance. Make sure to use transactions to group
72  * bulk insertKey or setKey as that is much faster.
73  *
74  * The examples/kvstore/kvstore.cpp is a simple speed test using a KVStore:
75  * @include examples/kvstore/kvstore.cpp
76  */
77  class KVStore {
78  public:
79 
80  /**
81  * Meta data concerning the key.
82  */
83  struct MetaData {
84  /** unix timestamp in UTC (seconds) */
85  double last_modified = 0;
86  /** number of times the value was updates (is 0 after insertKey) */
87  int64_t update_count = 0;
88  /** The DataType of the key's value. */
90  };
91 
92  /**
93  * Create the KVStore object against the path. If the KVStore does not exist yet, it is
94  * created. If it already exists, it is opened.
95  * @param path The filesystem path of the KVStore file.
96  */
97  KVStore( const std::filesystem::path &path );
98 
99  /**
100  * Destructor, cleanup sync and close the SQLLite database.
101  */
102  ~KVStore();
103 
104  /**
105  * Sync all to disk - issue a SQLite full checkpoint.
106  */
107  void checkpoint();
108 
109  /**
110  * Commit a transaction. Throws a dodo::common::Exception when no transaction has started.
111  */
112  void commitTransaction();
113 
114  /**
115  * Delete the key or return false if the key does not exist.
116  * @param key The key.
117  * @return True if the key existed.
118  */
119  bool deleteKey( const std::string &key );
120 
121  /**
122  * If the key does not exist, create it with the default and return the default.
123  * If the key exists, return its value (which may not be the default).
124  * @param key The key.
125  * @param def The string value for the key if it does not exist.
126  * @return The key value.
127  */
128  std::string ensureWithDefault( const std::string &key, const std::string &def );
129 
130  /**
131  * If the key does not exist, create it with the default and return the default.
132  * If the key exists, return its value (which may not be the default).
133  * @param key The key.
134  * @param def The double value for the key if it does not exist.
135  * @return The key value.
136  */
137  double ensureWithDefault( const std::string &key, double &def );
138 
139  /**
140  * If the key does not exist, create it with the default and return the default.
141  * If the key exists, return its value (which may not be the default).
142  * @param key The key.
143  * @param def The int64_t value for the key if it does not exist.
144  * @return The key value.
145  */
146  int64_t ensureWithDefault( const std::string &key, int64_t &def );
147 
148  /**
149  * Check if the key exists. Unless the value is not required , it is more efficient
150  * to get the value in one go by calling the getString(), getDouble(), getInt64() and getData()
151  * members, which return false if the key does not exist.
152  * @param key The key to check for.
153  * @return False if the key does not exist.
154  */
155  bool exists( const std::string &key ) const;
156 
157  /**
158  * Return a list of keys that match the filter.
159  * @param keys The list that receives the keys. The list is cleared before assigning keys so it may turn up empty if
160  * the filter matches no keys.
161  * @param filter The SQL-style case-sensitive filter as in '%match%', 'match%'
162  */
163  void filterKeys( std::list<std::string>& keys, const std::string &filter ) const;
164 
165  /**
166  * Get MetaData for the key.
167  * @param key The key.
168  * @return the MetaData for the key.
169  */
170  MetaData getMetaData( const std::string &key ) const;
171 
172  /**
173  * If the key exists, returns true and sets the value parameter.
174  * @param key The key to get the value for.
175  * @param value The destination value to set.
176  * @return False if the key does not exist.
177  */
178  bool getValue( const std::string &key, std::string &value ) const;
179 
180  /**
181  * If the key exists, returns true and sets the value parameter.
182  * @param key The key to get the value for.
183  * @param value The destination value to set.
184  * @return False if the key does not exist.
185  */
186  bool getValue( const std::string &key, double &value ) const;
187 
188  /**
189  * If the key exists, returns true and sets the value parameter.
190  * @param key The key to get the value for.
191  * @param value The destination value to set.
192  * @return False if the key does not exist.
193  */
194  bool getValue( const std::string &key, int64_t &value ) const;
195 
196  /**
197  * If the key exists, returns true and copies (overwrites) data to the Bytes.
198  * @param key The key to get the value for.
199  * @param value The Bytes that will receive the data.
200  * @return False if the key does not exist.
201  */
202  bool getValue( const std::string &key, common::Bytes &value ) const;
203 
204  /**
205  * Insert a (key, string) pair.
206  * @param key The key.
207  * @param value The string value to set.
208  * @return True if the key was created, false if the key already exists.
209  */
210  bool insertKey( const std::string &key, const std::string &value );
211 
212  /**
213  * Insert a (key, double) pair.
214  * @param key The key.
215  * @param value The double value to set.
216  * @return True if the key was created, false if the key already exists.
217  */
218  bool insertKey( const std::string &key, const double &value );
219 
220  /**
221  * Insert a (key, int64_t) pair.
222  * @param key The key.
223  * @param value The int64_t value to set.
224  * @return True if the key was created, false if the key already exists.
225  */
226  bool insertKey( const std::string &key, const int64_t &value );
227 
228  /**
229  * Insert a (key, Bytes) pair. Note that the size of the data cannot exceed INT_MAX, and
230  * exception is thrown if the Bytes is larger.
231  * @param key The key.
232  * @param value The Bytes to insert.
233  * @return True if the key was created, false if the key already exists.
234  */
235  bool insertKey( const std::string &key, const common::Bytes &value );
236 
237  /**
238  * Optimzime, preferably called after workload and implicitly called by the destructor.
239  */
240  void optimize();
241 
242  /**
243  * Rollback a transaction. Throws a dodo::common::Exception when no transaction has started.
244  */
245  void rollbackTransaction();
246 
247  /**
248  * Set the string value of an existing key. The function returns false if the key does not exist.
249  * @param key The key.
250  * @param value The string value to set.
251  * @return True if the key exists, in which case it is also updated.
252  */
253  bool setKey( const std::string &key, const std::string &value );
254 
255  /**
256  * Set the double value of an existing key. The function returns false if the key does not exist.
257  * @param key The key.
258  * @param value The double value to set.
259  * @return True if the key exists, in which case it is also updated.
260  */
261  bool setKey( const std::string &key, const double &value );
262 
263  /**
264  * Set the int64_t value of an existing key. The function returns false if the key does not exist.
265  * @param key The key.
266  * @param value The int64_t value to set.
267  * @return True if the key exists, in which case it is also updated.
268  */
269  bool setKey( const std::string &key, const int64_t &value );
270 
271  /**
272  * Set the binary data/Bytes value of an existing key. The function returns false if the key does not exist.
273  * @param key The key.
274  * @param value The Bytes to set.
275  * @return True if the key exists, in which case it is also updated.
276  */
277  bool setKey( const std::string &key, const common::Bytes &value );
278 
279  /**
280  * Start a transaction. If insertKey or setKey calls are not inside a started transaction, each will commit
281  * automatically and indvidually, which is mach (much!) slower for bulk operations.
282  */
283  void startTransaction();
284 
285  /**
286  * Vaccum - clean and defragment, which may increase efficiency after heavy deletion and/or modification.
287  */
288  void vacuum();
289 
290  protected:
291 
292  /**
293  * Create the SQLite schema.
294  */
295  void createSchema();
296 
297  /**
298  * Prepare all SQL statements.
299  */
300  void prepareSQL();
301 
302  /** The filesystem path to the kvstore. */
303  std::filesystem::path path_;
304 
305  /** The database handle */
307 
308  /** check key existence statement handle. */
310 
311  /** Get-value-for-key statement handle. */
313 
314  /** Insert key pair statement handle. */
316 
317  /** Delete key pair statement handle. */
319 
320  /** Update key pair statement handle. */
322 
323  /** Key filter statement handle. */
325 
326  /** Key + value filter statement handle. */
328 
329  /** Get metadata statement handle. */
331  };
332 
333 }
334 
335 #endif
dodo::persist::KVStore
A persistent, multi-threaded key-value store backed by sqlite3 database (file).
Definition: kvstore.hpp:77
dodo::persist::KVStore::MetaData::update_count
int64_t update_count
number of times the value was updates (is 0 after insertKey)
Definition: kvstore.hpp:87
dodo::persist::KVStore::getValue
bool getValue(const std::string &key, std::string &value) const
If the key exists, returns true and sets the value parameter.
Definition: kvstore.cpp:156
dodo::persist::KVStore::stmt_metadata_
sqlite::Query * stmt_metadata_
Get metadata statement handle.
Definition: kvstore.hpp:330
dodo::persist::KVStore::stmt_getvalue_
sqlite::Query * stmt_getvalue_
Get-value-for-key statement handle.
Definition: kvstore.hpp:312
dodo::persist::KVStore::rollbackTransaction
void rollbackTransaction()
Rollback a transaction.
Definition: kvstore.cpp:256
dodo::persist::KVStore::getMetaData
MetaData getMetaData(const std::string &key) const
Get MetaData for the key.
Definition: kvstore.cpp:144
dodo::persist::sqlite::Query
Queries can take bind values and return select lists.
Definition: sqlite.hpp:387
dodo::common::Bytes
An array of Octets with size elements.
Definition: bytes.hpp:44
dodo::persist::KVStore::exists
bool exists(const std::string &key) const
Check if the key exists.
Definition: kvstore.cpp:127
dodo::persist::KVStore::MetaData::last_modified
double last_modified
unix timestamp in UTC (seconds)
Definition: kvstore.hpp:85
dodo::persist::KVStore::path_
std::filesystem::path path_
The filesystem path to the kvstore.
Definition: kvstore.hpp:303
dodo::persist::KVStore::startTransaction
void startTransaction()
Start a transaction.
Definition: kvstore.cpp:292
dodo::persist::KVStore::checkpoint
void checkpoint()
Sync all to disk - issue a SQLite full checkpoint.
Definition: kvstore.cpp:64
dodo::persist::KVStore::stmt_insert_
sqlite::DML * stmt_insert_
Insert key pair statement handle.
Definition: kvstore.hpp:315
dodo::persist::KVStore::MetaData
Meta data concerning the key.
Definition: kvstore.hpp:83
dodo::persist::KVStore::stmt_key_value_filter_
sqlite::Query * stmt_key_value_filter_
Key + value filter statement handle.
Definition: kvstore.hpp:327
dodo::persist::KVStore::setKey
bool setKey(const std::string &key, const std::string &value)
Set the string value of an existing key.
Definition: kvstore.cpp:260
dodo::persist::KVStore::insertKey
bool insertKey(const std::string &key, const std::string &value)
Insert a (key, string) pair.
Definition: kvstore.cpp:200
dodo::persist::sqlite::Query::DataType
DataType
The data type of a select-list value.
Definition: sqlite.hpp:393
dodo::persist::KVStore::prepareSQL
void prepareSQL()
Prepare all SQL statements.
Definition: kvstore.cpp:241
dodo::persist::KVStore::db_
sqlite::Database * db_
The database handle.
Definition: kvstore.hpp:306
dodo::persist::KVStore::KVStore
KVStore(const std::filesystem::path &path)
Create the KVStore object against the path.
Definition: kvstore.cpp:34
dodo::persist::KVStore::filterKeys
void filterKeys(std::list< std::string > &keys, const std::string &filter) const
Return a list of keys that match the filter.
Definition: kvstore.cpp:135
dodo::persist::KVStore::commitTransaction
void commitTransaction()
Commit a transaction.
Definition: kvstore.cpp:68
dodo::persist::KVStore::ensureWithDefault
std::string ensureWithDefault(const std::string &key, const std::string &def)
If the key does not exist, create it with the default and return the default.
Definition: kvstore.cpp:97
dodo::persist::KVStore::stmt_exists_
sqlite::Query * stmt_exists_
check key existence statement handle.
Definition: kvstore.hpp:309
dodo::persist::KVStore::optimize
void optimize()
Optimzime, preferably called after workload and implicitly called by the destructor.
Definition: kvstore.cpp:235
dodo::persist
Persistent storage structures.
Definition: kvstore.hpp:35
dodo::persist::sqlite::DML
Data Modification Language statements can take bind values.
Definition: sqlite.hpp:294
dodo::persist::KVStore::deleteKey
bool deleteKey(const std::string &key)
Delete the key or return false if the key does not exist.
Definition: kvstore.cpp:90
dodo::persist::KVStore::stmt_key_filter_
sqlite::Query * stmt_key_filter_
Key filter statement handle.
Definition: kvstore.hpp:324
dodo::persist::KVStore::~KVStore
~KVStore()
Destructor, cleanup sync and close the SQLLite database.
Definition: kvstore.cpp:50
dodo::persist::KVStore::createSchema
void createSchema()
Create the SQLite schema.
Definition: kvstore.cpp:72
dodo::persist::KVStore::MetaData::type
sqlite::Query::DataType type
The DataType of the key's value.
Definition: kvstore.hpp:89
dodo::persist::KVStore::stmt_update_
sqlite::DML * stmt_update_
Update key pair statement handle.
Definition: kvstore.hpp:321
dodo::persist::sqlite::Database
A STL friendly wrapper around the great sqlite3.
Definition: sqlite.hpp:52
dodo::persist::KVStore::vacuum
void vacuum()
Vaccum - clean and defragment, which may increase efficiency after heavy deletion and/or modification...
Definition: kvstore.cpp:298
dodo::persist::KVStore::stmt_delete_
sqlite::DML * stmt_delete_
Delete key pair statement handle.
Definition: kvstore.hpp:318
bytes.hpp