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()
- {
- if (std::uncaught_exception()) {
- mConnRef.exec("ROLLBACK TRANSACTION");
- } else {
- mConnRef.exec("COMMIT TRANSACTION");
- }
- }
-private:
- sqlite3::Connection& mConnRef;
-};
-
struct ScopedReset {
ScopedReset(std::unique_ptr<sqlite3::Statement>& stmtPtr)
: mStmtPtr(stmtPtr) {}
std::unique_ptr<sqlite3::Statement>& mStmtPtr;
};
-std::string escape(const std::string& in)
+/**
+ * Escape characters used by the GLOB function.
+ */
+void sqliteEscapeStr(::sqlite3_context* context, int argc, ::sqlite3_value** values)
{
- const std::set<char> toEscape({'?', '*', '[', ']'});
+ char* inBuff = (char*)sqlite3_value_text(values[0]);
+ if (argc != 1 || inBuff == NULL) {
+ sqlite3_result_error(context, "SQL function escapeSequence() called with invalid arguments.\n", -1);
+ return;
+ }
+
+ std::string in(inBuff);
+ static const std::set<char> toEscape({'?', '*', '[', ']'});
// Compute the out size
auto isEscapeChar = [&](char c) {
in.end(),
isEscapeChar);
if (numEscape == 0) {
- return in;
+ sqlite3_result_text(context, in.c_str(), AUTO_DETERM_SIZE, SQLITE_TRANSIENT);
}
// Escape characters
out[j] = in[i];
}
}
- return out;
+ sqlite3_result_text(context, out.c_str(), AUTO_DETERM_SIZE, SQLITE_TRANSIENT);
}
+
} // namespace
+
+struct KVStore::Transaction {
+ Transaction(KVStore* kvstorePtr)
+ : mKVStorePtr(kvstorePtr)
+ {
+ if (mKVStorePtr->mTransactionCounter == 0) {
+ mKVStorePtr->mConn.exec("BEGIN EXCLUSIVE TRANSACTION");
+ }
+ mKVStorePtr->mTransactionCounter += 1;
+ }
+
+ ~Transaction()
+ {
+ mKVStorePtr->mTransactionCounter -= 1;
+ if (mKVStorePtr->mTransactionCounter == 0) {
+ if (std::uncaught_exception()) {
+ mKVStorePtr->mConn.exec("ROLLBACK TRANSACTION");
+ } else {
+ mKVStorePtr->mConn.exec("COMMIT TRANSACTION");
+ }
+ }
+ }
+
+private:
+ config::KVStore* mKVStorePtr;
+};
+
+
KVStore::KVStore(const std::string& path)
: mPath(path),
mConn(path)
{
setupDb();
+ createFunctions();
prepareStatements();
}
: mPath(store.mPath),
mConn(mPath)
{
+ createFunctions();
prepareStatements();
}
void KVStore::setupDb()
{
Lock lock(mConnMtx);
- Transaction transaction(mConn);
+ Transaction transaction(this);
mConn.exec("CREATE TABLE IF NOT EXISTS data (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
}
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"));
+ new sqlite3::Statement(mConn, "SELECT value FROM data WHERE key = ? LIMIT 1"));
+ mGetKeyExistsStmt.reset(
+ new sqlite3::Statement(mConn, "SELECT 1 FROM data WHERE key = ?1 OR key GLOB escapeStr(?1) || '.*' LIMIT 1"));
+ mGetIsEmptyStmt.reset(
+ new sqlite3::Statement(mConn, "SELECT 1 FROM data LIMIT 1"));
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 ? ||'*' "));
+ new sqlite3::Statement(mConn, "DELETE FROM data WHERE key = ?1 OR key GLOB escapeStr(?1) ||'.*' "));
+}
+
+void KVStore::createFunctions()
+{
+ int ret = sqlite3_create_function(mConn.get(), "escapeStr", 1, SQLITE_ANY, 0, &sqliteEscapeStr, 0, 0);
+ if (ret != SQLITE_OK) {
+ throw ConfigException("Error during creating functions: " + mConn.getErrorMessage());
+ }
}
void KVStore::clear()
{
Lock lock(mConnMtx);
- Transaction transaction(mConn);
+ Transaction transaction(this);
mConn.exec("DELETE FROM data");
}
-unsigned int KVStore::size()
+bool KVStore::isEmpty()
{
Lock lock(mConnMtx);
- ScopedReset scopedReset(mGetSizeStmt);
+ ScopedReset scopedReset(mGetIsEmptyStmt);
- if (::sqlite3_step(mGetSizeStmt->get()) != SQLITE_ROW) {
+ int ret = ::sqlite3_step(mGetIsEmptyStmt->get());
+ if (ret == SQLITE_DONE) {
+ return true;
+ } else if (ret == SQLITE_ROW) {
+ return false;
+ } else {
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)
+bool KVStore::exists(const std::string& key)
{
Lock lock(mConnMtx);
- Transaction transaction(mConn);
- return countInternal(key);
-}
+ ScopedReset scopedReset(mGetKeyExistsStmt);
-unsigned int KVStore::countInternal(const std::string& key)
-{
- ScopedReset scopedReset(mGetValueCountStmt);
-
- ::sqlite3_bind_text(mGetValueCountStmt->get(), 1, escape(key).c_str(), AUTO_DETERM_SIZE, SQLITE_TRANSIENT);
+ ::sqlite3_bind_text(mGetKeyExistsStmt->get(), 1, key.c_str(), AUTO_DETERM_SIZE, SQLITE_TRANSIENT);
- if (::sqlite3_step(mGetValueCountStmt->get()) != SQLITE_ROW) {
+ int ret = ::sqlite3_step(mGetKeyExistsStmt->get());
+ if (ret == SQLITE_DONE) {
+ return false;
+ } else if (ret == SQLITE_ROW) {
+ return true;
+ } else {
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);
+ Transaction transaction(this);
removeInternal(key);
}
::sqlite3_bind_text(mSetValueStmt->get(), 1, key.c_str(), AUTO_DETERM_SIZE, SQLITE_STATIC);
::sqlite3_bind_text(mSetValueStmt->get(), 2, value.c_str(), AUTO_DETERM_SIZE, SQLITE_STATIC);
- Transaction transaction(mConn);
+ Transaction transaction(this);
ScopedReset scopedReset(mSetValueStmt);
if (::sqlite3_step(mSetValueStmt->get()) != SQLITE_DONE) {
}
Lock lock(mConnMtx);
- Transaction transaction(mConn);
+ Transaction transaction(this);
removeInternal(key);
- for (unsigned int i = 0; i < values.size(); ++i) {
- ScopedReset scopedReset(mSetValueStmt);
- const std::string modifiedKey = key + "." + std::to_string(i);;
-
- ::sqlite3_bind_text(mSetValueStmt->get(), 1, modifiedKey.c_str(), AUTO_DETERM_SIZE, SQLITE_STATIC);
- ::sqlite3_bind_text(mSetValueStmt->get(), 2, values[i].c_str(), AUTO_DETERM_SIZE, SQLITE_STATIC);
+ // Save vector's capacity
+ setInternal(key, values.size());
- if (::sqlite3_step(mSetValueStmt->get()) != SQLITE_DONE) {
- throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
- }
+ // Save vector's elements
+ for (unsigned int i = 0; i < values.size(); ++i) {
+ setInternal(config::key(key, std::to_string(i)),
+ values[i]);
}
}
{
Lock lock(mConnMtx);
- ::sqlite3_bind_text(mGetValueStmt->get(), 1, escape(key).c_str(), AUTO_DETERM_SIZE, SQLITE_TRANSIENT);
+ ::sqlite3_bind_text(mGetValueStmt->get(), 1, key.c_str(), AUTO_DETERM_SIZE, SQLITE_TRANSIENT);
- Transaction transaction(mConn);
+ Transaction transaction(this);
ScopedReset scopedReset(mGetValueStmt);
int ret = ::sqlite3_step(mGetValueStmt->get());
if (ret == SQLITE_DONE) {
- throw ConfigException("No value corresponding to the key");
+ throw ConfigException("No value corresponding to the key: " + key);
}
if (ret != SQLITE_ROW) {
throw ConfigException("Error during stepping: " + mConn.getErrorMessage());
std::vector<std::string> KVStore::getInternal(const std::string& key, std::vector<std::string>*)
{
Lock lock(mConnMtx);
+ Transaction transaction(this);
- ::sqlite3_bind_text(mGetValueListStmt->get(), 1, escape(key).c_str(), AUTO_DETERM_SIZE, SQLITE_TRANSIENT);
-
- Transaction transaction(mConn);
- ScopedReset scopedReset(mGetValueListStmt);
-
- unsigned int valuesSize = countInternal(key);
+ unsigned int valuesSize = get<unsigned int>(key);
+ std::vector<std::string> values(valuesSize);
if (valuesSize == 0) {
- throw ConfigException("No value corresponding to the key");
+ return values;
}
- 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));
+ for (unsigned int i = 0; i < values.size(); ++i) {
+ values[i] = getInternal(config::key(key, std::to_string(i)),
+ static_cast<std::string*>(nullptr));
+
}
return values;