+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact: Jan Olszak <j.olszak@samsung.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+/**
+ * @file
+ * @author Jan Olszak (j.olszak@samsung.com)
+ * @brief Definition of a class for key-value storage in a sqlite3 database
+ */
+
+
+#include "config/kvstore.hpp"
+#include "config/exception.hpp"
+
+#include <limits>
+#include <memory>
+#include <algorithm>
+#include <set>
+
+namespace config {
+
+namespace {
+
+const int AUTO_DETERM_SIZE = -1;
+const int FIRST_COLUMN = 0;
+
+struct Transaction {
+ Transaction(sqlite3::Connection& connRef)
+ : mConnRef(connRef)
+ {
+ mConnRef.exec("BEGIN EXCLUSIVE TRANSACTION");
+ }
+ ~Transaction()
+ {
+ mConnRef.exec("COMMIT TRANSACTION");
+ }
+private:
+ sqlite3::Connection& mConnRef;
+};
+
+
+std::string escape(const std::string& in)
+{
+ const std::set<char> toEscape({'?', '*', '[', ']'});
+
+ // Compute the out size
+ auto isEscapeChar = [&](char c) {
+ return toEscape.count(c) == 1;
+ };
+ size_t numEscape = std::count_if(in.begin(),
+ in.end(),
+ isEscapeChar);
+ if (numEscape == 0) {
+ return in;
+ }
+
+ // Escape characters
+ std::string out(in.size() + 2 * numEscape, 'x');
+ for (size_t i = 0, j = 0;
+ i < in.size();
+ ++i, ++j) {
+ if (isEscapeChar(in[i])) {
+ out[j] = '[';
+ ++j;
+ out[j] = in[i];
+ ++j;
+ out[j] = ']';
+ } else {
+ out[j] = in[i];
+ }
+ }
+ return out;
+}
+
+} // namespace
+
+KVStore::KVStore(const std::string& path)
+ : mConn(path)
+{
+ setupDb();
+ prepareStatements();
+}
+
+KVStore::~KVStore()
+{
+
+}
+
+void KVStore::setupDb()
+{
+ Lock lock(mConnMtx);
+ const std::string setupScript = R"setupScript(
+ BEGIN EXCLUSIVE TRANSACTION;
+
+ CREATE TABLE IF NOT EXISTS data (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL
+ );
+
+ COMMIT TRANSACTION;
+ )setupScript";
+
+ mConn.exec(setupScript);
+}
+
+void KVStore::prepareStatements()
+{
+ mGetValueStmt.reset(
+ new sqlite3::Statement(mConn, "SELECT value FROM data WHERE key GLOB ? LIMIT 1"));
+ mGetValueListStmt.reset(
+ new sqlite3::Statement(mConn, "SELECT value FROM data WHERE key GLOB ? ||'*' ORDER BY key"));
+ mGetValueCountStmt.reset(
+ new sqlite3::Statement(mConn, "SELECT count(key) FROM data WHERE key GLOB ? ||'*' "));
+ mGetSizeStmt.reset(
+ new sqlite3::Statement(mConn, "SELECT count(key) FROM data"));
+ mSetValueStmt.reset(
+ new sqlite3::Statement(mConn, "INSERT OR REPLACE INTO data (key, value) VALUES (?,?)"));
+ mRemoveValuesStmt.reset(
+ new sqlite3::Statement(mConn, "DELETE FROM data WHERE key GLOB ? ||'*' "));
+
+}
+
+void KVStore::clear()
+{
+ Lock lock(mConnMtx);
+ Transaction transaction(mConn);
+ mConn.exec("DELETE FROM data");
+}
+
+unsigned int KVStore::size()
+{
+ mGetSizeStmt->reset();
+
+ if (::sqlite3_step(mGetSizeStmt->get()) != SQLITE_ROW) {
+ throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
+ }
+
+ return static_cast<unsigned int>(::sqlite3_column_int(mGetSizeStmt->get(), FIRST_COLUMN));
+}
+
+unsigned int KVStore::count(const std::string& key)
+{
+ Lock lock(mConnMtx);
+ Transaction transaction(mConn);
+ return countInternal(key);
+}
+
+unsigned int KVStore::countInternal(const std::string& key)
+{
+ mGetValueCountStmt->reset();
+
+ ::sqlite3_bind_text(mGetValueCountStmt->get(), 1, escape(key).c_str(), AUTO_DETERM_SIZE, 0);
+
+ if (::sqlite3_step(mGetValueCountStmt->get()) != SQLITE_ROW) {
+ throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
+ }
+
+ return static_cast<unsigned int>(::sqlite3_column_int(mGetValueCountStmt->get(), FIRST_COLUMN));
+}
+
+void KVStore::remove(const std::string& key)
+{
+ Lock lock(mConnMtx);
+ Transaction transaction(mConn);
+ removeInternal(key);
+}
+
+void KVStore::removeInternal(const std::string& key)
+{
+ mRemoveValuesStmt->reset();
+ ::sqlite3_bind_text(mRemoveValuesStmt->get(), 1, key.c_str(), AUTO_DETERM_SIZE, 0);
+
+ if (::sqlite3_step(mRemoveValuesStmt->get()) != SQLITE_DONE) {
+ throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
+ }
+}
+
+void KVStore::set(const std::string& key, const std::string& value)
+{
+ Lock lock(mConnMtx);
+ mSetValueStmt->reset();
+
+ ::sqlite3_bind_text(mSetValueStmt->get(), 1, key.c_str(), AUTO_DETERM_SIZE, 0);
+ ::sqlite3_bind_text(mSetValueStmt->get(), 2, value.c_str(), AUTO_DETERM_SIZE, 0);
+
+ Transaction transaction(mConn);
+ if (::sqlite3_step(mSetValueStmt->get()) != SQLITE_DONE) {
+ throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
+ }
+}
+
+void KVStore::set(const std::string& key, const std::initializer_list<std::string>& values)
+{
+ set(key, std::vector<std::string>(values));
+}
+
+void KVStore::set(const std::string& key, const std::vector<std::string>& values)
+{
+ if (values.size() > std::numeric_limits<unsigned int>::max()) {
+ throw ConfigException("Too many values to insert");
+ }
+
+ Lock lock(mConnMtx);
+ Transaction transaction(mConn);
+
+ removeInternal(key);
+
+ for (unsigned int i = 0; i < values.size(); ++i) {
+ mSetValueStmt->reset();
+ const std::string modifiedKey = key + "." + std::to_string(i);;
+
+ ::sqlite3_bind_text(mSetValueStmt->get(), 1, modifiedKey.c_str(), AUTO_DETERM_SIZE, 0);
+ ::sqlite3_bind_text(mSetValueStmt->get(), 2, values[i].c_str(), AUTO_DETERM_SIZE, 0);
+
+ if (::sqlite3_step(mSetValueStmt->get()) != SQLITE_DONE) {
+ throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
+ }
+ }
+}
+
+std::string KVStore::get(const std::string& key)
+{
+ Lock lock(mConnMtx);
+
+ mGetValueStmt->reset();
+ ::sqlite3_bind_text(mGetValueStmt->get(), 1, escape(key).c_str(), AUTO_DETERM_SIZE, 0);
+
+ Transaction transaction(mConn);
+
+ int ret = ::sqlite3_step(mGetValueStmt->get());
+ if (ret == SQLITE_DONE) {
+ throw ConfigException("No value corresponding to the key");
+ }
+ if (ret != SQLITE_ROW) {
+ throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
+ }
+
+ return reinterpret_cast<const char*>(sqlite3_column_text(mGetValueStmt->get(), FIRST_COLUMN));
+}
+
+std::vector<std::string> KVStore::list(const std::string& key)
+{
+ Lock lock(mConnMtx);
+
+ mGetValueListStmt->reset();
+ ::sqlite3_bind_text(mGetValueListStmt->get(), 1, escape(key).c_str(), AUTO_DETERM_SIZE, 0);
+
+ Transaction transaction(mConn);
+
+ unsigned int valuesSize = countInternal(key);
+ if (valuesSize == 0) {
+ throw ConfigException("No value corresponding to the key");
+ }
+
+ std::vector<std::string> values(valuesSize);
+ for (std::string& value : values) {
+ if (::sqlite3_step(mGetValueListStmt->get()) != SQLITE_ROW) {
+ throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
+ }
+ value = reinterpret_cast<const char*>(
+ sqlite3_column_text(mGetValueListStmt->get(), FIRST_COLUMN));
+ }
+
+ return values;
+}
+} // namespace config