Dynamic configuration stored in a database
[archive/platform/core/system/libConfig.git] / src / config / kvstore.cpp
1 /*
2  *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *  Contact: Jan Olszak <j.olszak@samsung.com>
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License
17  */
18
19 /**
20  * @file
21  * @author Jan Olszak (j.olszak@samsung.com)
22  * @brief  Definition of a class for key-value storage in a sqlite3 database
23  */
24
25
26 #include "config/kvstore.hpp"
27 #include "config/exception.hpp"
28
29 #include <limits>
30 #include <memory>
31 #include <algorithm>
32 #include <set>
33
34 namespace config {
35
36 namespace {
37
38 const int AUTO_DETERM_SIZE = -1;
39 const int FIRST_COLUMN = 0;
40
41 struct Transaction {
42     Transaction(sqlite3::Connection& connRef)
43         : mConnRef(connRef)
44     {
45         mConnRef.exec("BEGIN EXCLUSIVE TRANSACTION");
46     }
47     ~Transaction()
48     {
49         mConnRef.exec("COMMIT TRANSACTION");
50     }
51 private:
52     sqlite3::Connection& mConnRef;
53 };
54
55
56 std::string escape(const std::string& in)
57 {
58     const std::set<char> toEscape({'?', '*', '[', ']'});
59
60     // Compute the out size
61     auto isEscapeChar = [&](char c) {
62         return toEscape.count(c) == 1;
63     };
64     size_t numEscape = std::count_if(in.begin(),
65                                      in.end(),
66                                      isEscapeChar);
67     if (numEscape == 0) {
68         return in;
69     }
70
71     // Escape characters
72     std::string out(in.size() + 2 * numEscape, 'x');
73     for (size_t i = 0, j = 0;
74             i < in.size();
75             ++i, ++j) {
76         if (isEscapeChar(in[i])) {
77             out[j] = '[';
78             ++j;
79             out[j] = in[i];
80             ++j;
81             out[j] = ']';
82         } else {
83             out[j] = in[i];
84         }
85     }
86     return out;
87 }
88
89 } // namespace
90
91 KVStore::KVStore(const std::string& path)
92     : mConn(path)
93 {
94     setupDb();
95     prepareStatements();
96 }
97
98 KVStore::~KVStore()
99 {
100
101 }
102
103 void KVStore::setupDb()
104 {
105     Lock lock(mConnMtx);
106     const std::string setupScript = R"setupScript(
107                                         BEGIN EXCLUSIVE TRANSACTION;
108
109                                         CREATE TABLE IF NOT EXISTS data (
110                                         key TEXT PRIMARY KEY,
111                                         value TEXT NOT NULL
112                                         );
113
114                                         COMMIT TRANSACTION;
115                                       )setupScript";
116
117     mConn.exec(setupScript);
118 }
119
120 void KVStore::prepareStatements()
121 {
122     mGetValueStmt.reset(
123         new sqlite3::Statement(mConn, "SELECT value FROM data WHERE key GLOB ? LIMIT 1"));
124     mGetValueListStmt.reset(
125         new sqlite3::Statement(mConn, "SELECT value FROM data WHERE key GLOB ? ||'*' ORDER BY key"));
126     mGetValueCountStmt.reset(
127         new sqlite3::Statement(mConn, "SELECT count(key) FROM data WHERE key GLOB ? ||'*' "));
128     mGetSizeStmt.reset(
129         new sqlite3::Statement(mConn, "SELECT count(key) FROM data"));
130     mSetValueStmt.reset(
131         new sqlite3::Statement(mConn, "INSERT OR REPLACE INTO data (key, value) VALUES (?,?)"));
132     mRemoveValuesStmt.reset(
133         new sqlite3::Statement(mConn, "DELETE FROM data WHERE key GLOB ? ||'*' "));
134
135 }
136
137 void KVStore::clear()
138 {
139     Lock lock(mConnMtx);
140     Transaction transaction(mConn);
141     mConn.exec("DELETE FROM data");
142 }
143
144 unsigned int KVStore::size()
145 {
146     mGetSizeStmt->reset();
147
148     if (::sqlite3_step(mGetSizeStmt->get()) != SQLITE_ROW) {
149         throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
150     }
151
152     return static_cast<unsigned int>(::sqlite3_column_int(mGetSizeStmt->get(), FIRST_COLUMN));
153 }
154
155 unsigned int KVStore::count(const std::string& key)
156 {
157     Lock lock(mConnMtx);
158     Transaction transaction(mConn);
159     return countInternal(key);
160 }
161
162 unsigned int KVStore::countInternal(const std::string& key)
163 {
164     mGetValueCountStmt->reset();
165
166     ::sqlite3_bind_text(mGetValueCountStmt->get(), 1, escape(key).c_str(), AUTO_DETERM_SIZE, 0);
167
168     if (::sqlite3_step(mGetValueCountStmt->get()) != SQLITE_ROW) {
169         throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
170     }
171
172     return static_cast<unsigned int>(::sqlite3_column_int(mGetValueCountStmt->get(), FIRST_COLUMN));
173 }
174
175 void KVStore::remove(const std::string& key)
176 {
177     Lock lock(mConnMtx);
178     Transaction transaction(mConn);
179     removeInternal(key);
180 }
181
182 void KVStore::removeInternal(const std::string& key)
183 {
184     mRemoveValuesStmt->reset();
185     ::sqlite3_bind_text(mRemoveValuesStmt->get(), 1, key.c_str(), AUTO_DETERM_SIZE, 0);
186
187     if (::sqlite3_step(mRemoveValuesStmt->get()) != SQLITE_DONE) {
188         throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
189     }
190 }
191
192 void KVStore::set(const std::string& key, const std::string& value)
193 {
194     Lock lock(mConnMtx);
195     mSetValueStmt->reset();
196
197     ::sqlite3_bind_text(mSetValueStmt->get(), 1, key.c_str(), AUTO_DETERM_SIZE, 0);
198     ::sqlite3_bind_text(mSetValueStmt->get(), 2, value.c_str(), AUTO_DETERM_SIZE, 0);
199
200     Transaction transaction(mConn);
201     if (::sqlite3_step(mSetValueStmt->get()) != SQLITE_DONE) {
202         throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
203     }
204 }
205
206 void KVStore::set(const std::string& key, const std::initializer_list<std::string>& values)
207 {
208     set(key, std::vector<std::string>(values));
209 }
210
211 void KVStore::set(const std::string& key, const std::vector<std::string>& values)
212 {
213     if (values.size() > std::numeric_limits<unsigned int>::max()) {
214         throw ConfigException("Too many values to insert");
215     }
216
217     Lock lock(mConnMtx);
218     Transaction transaction(mConn);
219
220     removeInternal(key);
221
222     for (unsigned int i = 0; i < values.size(); ++i) {
223         mSetValueStmt->reset();
224         const std::string modifiedKey = key + "." + std::to_string(i);;
225
226         ::sqlite3_bind_text(mSetValueStmt->get(), 1, modifiedKey.c_str(), AUTO_DETERM_SIZE, 0);
227         ::sqlite3_bind_text(mSetValueStmt->get(), 2, values[i].c_str(), AUTO_DETERM_SIZE, 0);
228
229         if (::sqlite3_step(mSetValueStmt->get()) != SQLITE_DONE) {
230             throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
231         }
232     }
233 }
234
235 std::string KVStore::get(const std::string& key)
236 {
237     Lock lock(mConnMtx);
238
239     mGetValueStmt->reset();
240     ::sqlite3_bind_text(mGetValueStmt->get(), 1, escape(key).c_str(), AUTO_DETERM_SIZE, 0);
241
242     Transaction transaction(mConn);
243
244     int ret = ::sqlite3_step(mGetValueStmt->get());
245     if (ret == SQLITE_DONE) {
246         throw ConfigException("No value corresponding to the key");
247     }
248     if (ret != SQLITE_ROW) {
249         throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
250     }
251
252     return reinterpret_cast<const char*>(sqlite3_column_text(mGetValueStmt->get(), FIRST_COLUMN));
253 }
254
255 std::vector<std::string> KVStore::list(const std::string& key)
256 {
257     Lock lock(mConnMtx);
258
259     mGetValueListStmt->reset();
260     ::sqlite3_bind_text(mGetValueListStmt->get(), 1, escape(key).c_str(), AUTO_DETERM_SIZE, 0);
261
262     Transaction transaction(mConn);
263
264     unsigned int valuesSize = countInternal(key);
265     if (valuesSize == 0) {
266         throw ConfigException("No value corresponding to the key");
267     }
268
269     std::vector<std::string> values(valuesSize);
270     for (std::string& value : values) {
271         if (::sqlite3_step(mGetValueListStmt->get()) != SQLITE_ROW) {
272             throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
273         }
274         value = reinterpret_cast<const char*>(
275                     sqlite3_column_text(mGetValueListStmt->get(), FIRST_COLUMN));
276     }
277
278     return values;
279 }
280 } // namespace config