dodo  0.0.1
A C++ library to create containerized Linux services
kvstore.cpp
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.cpp
20  * Implements the dodo::persistent::KVStore class.
21  */
22 
23 
26 #include <common/exception.hpp>
27 #include <common/util.hpp>
28 
29 #include <iostream>
30 #include <climits>
31 
32 namespace dodo::persist {
33 
34  KVStore::KVStore( const std::filesystem::path &path ) {
35  path_ = path;
36  db_ = new sqlite::Database( path_ );
37 
38  stmt_exists_ = new sqlite::Query( *db_ );
39  stmt_getvalue_ = new sqlite::Query( *db_ );;
40  stmt_insert_ = new sqlite::DML( *db_ );;
41  stmt_delete_ = new sqlite::DML( *db_ );;
42  stmt_update_ = new sqlite::DML( *db_ );;
45  stmt_metadata_ = new sqlite::Query( *db_ );;;
46  createSchema();
47  prepareSQL();
48  }
49 
51  if ( stmt_metadata_ ) delete stmt_metadata_;
53  if ( stmt_key_filter_ ) delete stmt_key_filter_;
54  if ( stmt_update_ ) delete stmt_update_;
55  if ( stmt_delete_ ) delete stmt_delete_;
56  if ( stmt_insert_ ) delete stmt_insert_;
57  if ( stmt_getvalue_ ) delete stmt_getvalue_;
58  if ( stmt_exists_ ) delete stmt_exists_;
59  optimize();
60  checkpoint();
61  if( db_) delete db_;
62  }
63 
66  }
67 
69  db_->commit();
70  }
71 
73  {
74  sqlite::Query pragma( *db_ );
75  pragma.prepare( "PRAGMA journal_mode=WAL;" );
76  pragma.step();
77  }
78  {
79  sqlite::DDL ddl( *db_ );
80  ddl.prepare( "CREATE TABLE IF NOT EXISTS kvstore ( "
81  "key TEXT NOT NULL PRIMARY KEY, "
82  "value NOT NULL, "
83  "modified NUMBER NOT NULL DEFAULT ((julianday('now') - 2440587.5) * 86400.0), "
84  "updates INTEGER NOT NULL DEFAULT 0"
85  " )" );
86  ddl.execute();
87  }
88  }
89 
90  bool KVStore::deleteKey( const std::string &key ) {
91  stmt_delete_->bind( 1, key );
92  int affected = stmt_delete_->execute();
94  return (affected == 1);
95  }
96 
97  std::string KVStore::ensureWithDefault( const std::string &key, const std::string &def ) {
98  std::string result;
99  if ( getValue( key, result ) ) {
100  return result;
101  } else {
102  insertKey( key, def );
103  return def;
104  }
105  }
106 
107  double KVStore::ensureWithDefault( const std::string &key, double &def ) {
108  double result;
109  if ( getValue( key, result ) ) {
110  return result;
111  } else {
112  insertKey( key, def );
113  return def;
114  }
115  }
116 
117  int64_t KVStore::ensureWithDefault( const std::string &key, int64_t &def ) {
118  int64_t result;
119  if ( getValue( key, result ) ) {
120  return result;
121  } else {
122  insertKey( key, def );
123  return def;
124  }
125  }
126 
127  bool KVStore::exists( const std::string &key ) const {
128  stmt_exists_->bind( 1, key );
129  stmt_exists_->step();
130  int count = stmt_exists_->getInt(0);
131  stmt_exists_->reset();
132  return count == 1;
133  }
134 
135  void KVStore::filterKeys( std::list<std::string>& keys, const std::string &filter ) const {
136  keys.clear();
137  stmt_key_filter_->bind( 1, filter );
138  while ( stmt_key_filter_->step() ) {
139  keys.push_back( stmt_key_filter_->getText( 0 ) );
140  }
142  }
143 
144  KVStore::MetaData KVStore::getMetaData( const std::string &key ) const {
145  MetaData data;
146  stmt_metadata_->bind( 1, key );
147  if ( stmt_metadata_->step() ) {
149  data.update_count = stmt_metadata_->getInt64( 1 );
150  data.type = stmt_metadata_->getDataType( 2 );
151  }
153  return data;
154  }
155 
156  bool KVStore::getValue( const std::string &key, std::string &value ) const {
157  bool result = true;
158  stmt_getvalue_->bind( 1, key );
159  if ( stmt_getvalue_->step() ) {
160  value = stmt_getvalue_->getText(0);
161  result = true;
162  } else result = false;
164  return result;
165  }
166 
167  bool KVStore::getValue( const std::string &key, double &value ) const {
168  bool result = true;
169  stmt_getvalue_->bind( 1, key );
170  if ( stmt_getvalue_->step() ) {
171  value = stmt_getvalue_->getDouble(0);
172  result = true;
173  } else result = false;
175  return result;
176  }
177 
178  bool KVStore::getValue( const std::string &key, int64_t &value ) const {
179  bool result = true;
180  stmt_getvalue_->bind( 1, key );
181  if ( stmt_getvalue_->step() ) {
182  value = stmt_getvalue_->getInt64(0);
183  result = true;
184  } else result = false;
186  return result;
187  }
188 
189  bool KVStore::getValue( const std::string &key, common::Bytes &value ) const {
190  bool result = true;
191  stmt_getvalue_->bind( 1, key );
192  if ( stmt_getvalue_->step() ) {
193  stmt_getvalue_->getBytes( 0, value );
194  result = true;
195  } else result = false;
197  return result;
198  }
199 
200  bool KVStore::insertKey( const std::string &key, const std::string &value ) {
201  stmt_insert_->bind( 1, key );
202  stmt_insert_->bind( 2, value );
203  auto count = stmt_insert_->execute();
204  stmt_insert_->reset();
205  return count == 1;
206  }
207 
208  bool KVStore::insertKey( const std::string &key, const double &value ) {
209  bool result = true;
210  stmt_insert_->bind( 1, key );
211  stmt_insert_->bind( 2, value );
213  stmt_insert_->reset();
214  return result;
215  }
216 
217  bool KVStore::insertKey( const std::string &key, const int64_t &value ) {
218  bool result = true;
219  stmt_insert_->bind( 1, key );
220  stmt_insert_->bind( 2, value );
222  stmt_insert_->reset();
223  return result;
224  }
225 
226  bool KVStore::insertKey( const std::string &key, const common::Bytes &value ) {
227  bool result = true;
228  stmt_insert_->bind( 1, key );
229  stmt_insert_->bind( 2, value );
231  stmt_insert_->reset();
232  return result;
233  }
234 
236  sqlite::DDL ddl( *db_ );
237  ddl.prepare( "PRAGMA optimize;" );
238  ddl.execute();
239  }
240 
242  persist::sqlite::DDL ddl( *db_ );
243  ddl.prepare( "PRAGMA case_sensitive_like=ON;" );
244  ddl.execute();
245 
246  stmt_exists_->prepare( "SELECT COUNT(1) FROM kvstore WHERE key = ?" );
247  stmt_getvalue_->prepare( "SELECT value FROM kvstore WHERE key = ?" );
248  stmt_insert_->prepare( "INSERT INTO kvstore ( key, value ) VALUES ( ?, ? )" );
249  stmt_delete_->prepare( "DELETE FROM kvstore WHERE key = ?" );
250  stmt_update_->prepare( "UPDATE kvstore SET value = ?, modified = ((julianday('now') - 2440587.5) * 86400.0), updates = updates + 1 WHERE key = ?" );
251  stmt_key_filter_->prepare( "SELECT key FROM kvstore WHERE key LIKE ? ORDER BY key" );
252  stmt_key_value_filter_->prepare( "SELECT key, value FROM kvstore WHERE key LIKE ? ORDER BY key" );
253  stmt_metadata_->prepare( "SELECT modified, updates, value FROM kvstore WHERE key = ?" );
254  }
255 
257  db_->rollback();
258  }
259 
260  bool KVStore::setKey( const std::string &key, const std::string &value ) {
261  stmt_update_->bind( 1, value );
262  stmt_update_->bind( 2, key );
263  int rows = stmt_update_->execute();
264  stmt_update_->reset();
265  return rows == 1;
266  }
267 
268  bool KVStore::setKey( const std::string &key, const double &value ) {
269  stmt_update_->bind( 1, value );
270  stmt_update_->bind( 2, key );
271  int rows = stmt_update_->execute();
272  stmt_update_->reset();
273  return rows == 1;
274  }
275 
276  bool KVStore::setKey( const std::string &key, const int64_t &value ) {
277  stmt_update_->bind( 1, value );
278  stmt_update_->bind( 2, key );
279  int rows = stmt_update_->execute();
280  stmt_update_->reset();
281  return rows == 1;
282  }
283 
284  bool KVStore::setKey( const std::string &key, const common::Bytes &value ) {
285  stmt_update_->bind( 1, value );
286  stmt_update_->bind( 2, key );
287  int rows = stmt_update_->execute();
288  stmt_update_->reset();
289  return rows == 1;
290  }
291 
293  sqlite::DDL ddl( *db_ );
294  ddl.prepare( "BEGIN TRANSACTION" );
295  ddl.execute();
296  }
297 
299  sqlite::DDL ddl( *db_ );
300  ddl.prepare( "VACUUM;" );
301  ddl.execute();
302  }
303 
304 };
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::sqlite::Database::rollback
void rollback()
Rollback a transaction.
Definition: sqlite.cpp:255
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
sqlite.hpp
dodo::persist::KVStore::rollbackTransaction
void rollbackTransaction()
Rollback a transaction.
Definition: kvstore.cpp:256
dodo::persist::sqlite::Database::commit
void commit()
Commit a transaction.
Definition: sqlite.cpp:247
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::sqlite::Query::getText
std::string getText(int col) const
Get string value from select list.
Definition: sqlite.cpp:504
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::step
bool step()
Step the result list, end of list returns false.
Definition: sqlite.cpp:476
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::sqlite::Query::getDataType
DataType getDataType(int col) const
Get the dataype of a select list column.
Definition: sqlite.cpp:488
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
kvstore.hpp
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::sqlite::DML::bind
void bind(int position, double value)
Bind a double value to the bind at position.
Definition: sqlite.cpp:411
dodo::persist::sqlite::Statement::prepare
void prepare(const std::string &sql)
Prepare a SQL statement.
Definition: sqlite.cpp:357
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::sqlite::DML::execute
int execute()
Execute and return the number of rows affected.
Definition: sqlite.cpp:403
dodo::persist::KVStore::stmt_key_filter_
sqlite::Query * stmt_key_filter_
Key filter statement handle.
Definition: kvstore.hpp:324
dodo::persist::sqlite::Query::getInt64
int64_t getInt64(int col) const
Get int64_t value from select list.
Definition: sqlite.cpp:496
dodo::persist::KVStore::~KVStore
~KVStore()
Destructor, cleanup sync and close the SQLLite database.
Definition: kvstore.cpp:50
exception.hpp
dodo::persist::KVStore::createSchema
void createSchema()
Create the SQLite schema.
Definition: kvstore.cpp:72
dodo::persist::sqlite::Statement::reset
void reset(bool clear=true)
Reset a SQL statement for re-execute or even re-prepare.
Definition: sqlite.cpp:368
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::sqlite::DDL
Data Definition Language, SQL that takes no parameters, returns no data such as CREATE TABLE.
Definition: sqlite.hpp:261
dodo::persist::sqlite::Query::getDouble
double getDouble(int col) const
Get double value from select list.
Definition: sqlite.cpp:500
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::sqlite::DDL::execute
void execute()
execute, throws Oops on error.
Definition: sqlite.cpp:389
dodo::persist::KVStore::stmt_delete_
sqlite::DML * stmt_delete_
Delete key pair statement handle.
Definition: kvstore.hpp:318
dodo::persist::sqlite::Query::getBytes
void getBytes(int col, common::Bytes &bytes) const
Get Bytes value from select list.
Definition: sqlite.cpp:509
dodo::persist::sqlite::Database::checkPointFull
void checkPointFull()
Issue a full checpoint.
Definition: sqlite.cpp:295
dodo::persist::sqlite::Query::getInt
int getInt(int col) const
Get int value from select list.
Definition: sqlite.cpp:492
util.hpp