summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Seigo <aseigo@kde.org>2014-12-04 15:49:02 +0100
committerAaron Seigo <aseigo@kde.org>2014-12-04 15:49:02 +0100
commitef3b24358e508c5220d8b2548ec7207936794c66 (patch)
treea6cdaf531e9ba3b7d7c6c8030bad0fed102ca1a5
parent66f871ca418e099dbb13614a613e78e556292c0b (diff)
downloadsink-ef3b24358e508c5220d8b2548ec7207936794c66.tar.gz
sink-ef3b24358e508c5220d8b2548ec7207936794c66.zip
hide the implementation detail of which key/value store we use
also makes transactions "implicit" where necessary. this will make trying out other k/v stores a bit easier now as well.
-rw-r--r--store/database.cpp239
-rw-r--r--store/database.h35
-rw-r--r--store/test/storagebenchmark.cpp32
3 files changed, 186 insertions, 120 deletions
diff --git a/store/database.cpp b/store/database.cpp
index 2d93266..22d0693 100644
--- a/store/database.cpp
+++ b/store/database.cpp
@@ -1,134 +1,188 @@
1#include "database.h" 1#include "database.h"
2 2
3#include <iostream> 3#include <iostream>
4
5#include <QAtomicInt>
6#include <QDebug>
4#include <QDir> 7#include <QDir>
8#include <QReadWriteLock>
5#include <QString> 9#include <QString>
6#include <QTime> 10#include <QTime>
7#include <qdebug.h>
8 11
9Database::Database(const QString &path) 12#include <lmdb.h>
13
14class Database::Private
10{ 15{
11 int rc; 16public:
17 Private(const QString &path);
18 ~Private();
12 19
20 MDB_dbi dbi;
21 MDB_env *env;
22 MDB_txn *transaction;
23 bool readTransaction;
24};
25
26Database::Private::Private(const QString &path)
27 : transaction(0),
28 readTransaction(false)
29{
13 QDir dir; 30 QDir dir;
14 dir.mkdir(path); 31 dir.mkdir(path);
15 32
16 //create file 33 //create file
17 rc = mdb_env_create(&env); 34 if (mdb_env_create(&env)) {
18 rc = mdb_env_open(env, path.toStdString().data(), 0, 0664); 35 // TODO: handle error
19 const int dbSize = 10485760*100; //10MB * 100 36 } else {
20 mdb_env_set_mapsize(env, dbSize); 37 int rc = mdb_env_open(env, path.toStdString().data(), 0, 0664);
21 38
22 if (rc) { 39 if (rc) {
23 std::cerr << "mdb_env_open: " << rc << mdb_strerror(rc) << std::endl; 40 std::cerr << "mdb_env_open: " << rc << mdb_strerror(rc) << std::endl;
41 mdb_env_close(env);
42 env = 0;
43 } else {
44 const int dbSize = 10485760*100; //10MB * 100
45 mdb_env_set_mapsize(env, dbSize);
46 }
24 } 47 }
25} 48}
26 49
27Database::~Database() 50Database::Private::~Private()
28{ 51{
29 mdb_close(env, dbi); 52 if (transaction) {
53 mdb_txn_abort(transaction);
54 }
55
56 // it is still there and still unused, so we can shut it down
57 mdb_dbi_close(env, dbi);
30 mdb_env_close(env); 58 mdb_env_close(env);
31} 59}
32 60
33MDB_txn *Database::startTransaction() 61Database::Database(const QString &path)
62 : d(new Private(path))
63{
64}
65
66Database::~Database()
67{
68 delete d;
69}
70
71bool Database::isInTransaction() const
72{
73 return d->transaction;
74}
75
76bool Database::startTransaction(TransactionType type)
34{ 77{
78 if (!d->env) {
79 return false;
80 }
81
82 bool requestedRead = type == ReadOnly;
83 if (d->transaction && (!d->readTransaction || requestedRead)) {
84 return true;
85 }
86
87 if (d->transaction) {
88 // we are about to turn a read transaction into a writable one
89 abortTransaction();
90 }
91
92 //TODO handle errors
35 int rc; 93 int rc;
36 MDB_txn *transaction; 94 rc = mdb_txn_begin(d->env, NULL, requestedRead ? MDB_RDONLY : 0, &d->transaction);
37 rc = mdb_txn_begin(env, NULL, 0, &transaction); 95 if (!rc) {
38 rc = mdb_open(transaction, NULL, 0, &dbi); 96 rc = mdb_dbi_open(d->transaction, NULL, 0, &d->dbi);
39 return transaction; 97 }
98
99 return !rc;
40} 100}
41 101
42void Database::endTransaction(MDB_txn *transaction) 102bool Database::commitTransaction()
43{ 103{
104 if (!d->env) {
105 return false;
106 }
107
108 if (!d->transaction) {
109 return false;
110 }
111
44 int rc; 112 int rc;
45 rc = mdb_txn_commit(transaction); 113 rc = mdb_txn_commit(d->transaction);
114 d->transaction = 0;
115
46 if (rc) { 116 if (rc) {
47 std::cerr << "mdb_txn_commit: " << rc << mdb_strerror(rc) << std::endl; 117 std::cerr << "mdb_txn_commit: " << rc << mdb_strerror(rc) << std::endl;
48 } 118 }
119
120 return !rc;
49} 121}
50 122
123void Database::abortTransaction()
124{
125 if (!d->env || !d->transaction) {
126 return;
127 }
128
129 mdb_txn_abort(d->transaction);
130 d->transaction = 0;
131}
51 132
52void Database::write(const std::string &sKey, const std::string &sValue, MDB_txn *transaction) 133bool Database::write(const std::string &sKey, const std::string &sValue)
53{ 134{
135 if (!d->env) {
136 return false;
137 }
138
139 const bool implicitTransaction = !d->transaction || d->readTransaction;
140 if (implicitTransaction) {
141 // TODO: if this fails, still try the write below?
142 if (!startTransaction()) {
143 return false;
144 }
145 }
146
54 int rc; 147 int rc;
55 MDB_val key, data; 148 MDB_val key, data;
56 key.mv_size = sKey.size(); 149 key.mv_size = sKey.size();
57 key.mv_data = (void*)sKey.data(); 150 key.mv_data = (void*)sKey.data();
58 data.mv_size = sValue.size(); 151 data.mv_size = sValue.size();
59 data.mv_data = (void*)sValue.data(); 152 data.mv_data = (void*)sValue.data();
60 rc = mdb_put(transaction, dbi, &key, &data, 0); 153 rc = mdb_put(d->transaction, d->dbi, &key, &data, 0);
154
61 if (rc) { 155 if (rc) {
62 std::cerr << "mdb_put: " << rc << mdb_strerror(rc) << std::endl; 156 std::cerr << "mdb_put: " << rc << mdb_strerror(rc) << std::endl;
63 } 157 }
64}
65 158
66void Database::read(const std::string &sKey, const std::function<void(const std::string)> &resultHandler) 159 if (implicitTransaction) {
67{ 160 if (rc) {
68 int rc; 161 abortTransaction();
69 MDB_txn *txn;
70 MDB_val key;
71 MDB_val data;
72 MDB_cursor *cursor;
73
74 key.mv_size = sKey.size();
75 key.mv_data = (void*)sKey.data();
76
77 rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
78 rc = mdb_cursor_open(txn, dbi, &cursor);
79 if (sKey.empty()) {
80 std::cout << "Iterating over all values of store!" << std::endl;
81 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
82 const std::string resultKey(static_cast<char*>(key.mv_data), key.mv_size);
83 const std::string resultValue(static_cast<char*>(data.mv_data), data.mv_size);
84 // std::cout << "key: " << resultKey << " data: " << resultValue << std::endl;
85 resultHandler(resultValue);
86 }
87 } else {
88 if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_SET)) == 0) {
89 const std::string resultKey(static_cast<char*>(key.mv_data), key.mv_size);
90 const std::string resultValue(static_cast<char*>(data.mv_data), data.mv_size);
91 // std::cout << "key: " << resultKey << " data: " << resultValue << std::endl;
92 resultHandler(resultValue);
93 } else { 162 } else {
94 std::cout << "couldn't find value " << sKey << std::endl; 163 rc = commitTransaction();
95 } 164 }
96 } 165 }
97 if (rc) {
98 std::cerr << "mdb_cursor_get: " << rc << mdb_strerror(rc) << std::endl;
99 }
100 mdb_cursor_close(cursor);
101 mdb_txn_abort(txn);
102}
103 166
104 167 return !rc;
105
106ReadTransaction::ReadTransaction(const QString &path)
107{
108 int rc;
109
110 //create file
111 rc = mdb_env_create(&env);
112 //FIXME MDB_NOTLS required to so we can have multiple read-only transactions per resource?
113 rc = mdb_env_open(env, path.toStdString().data(), 0, 0664);
114 const int dbSize = 10485760*100; //10MB * 100
115 mdb_env_set_mapsize(env, dbSize);
116
117 if (rc) {
118 std::cerr << "mdb_env_open: " << rc << mdb_strerror(rc) << std::endl;
119 }
120} 168}
121 169
122ReadTransaction::~ReadTransaction() 170void Database::read(const std::string &sKey, const std::function<void(const std::string &value)> &resultHandler)
123{ 171{
124 mdb_txn_abort(txn); 172 read(sKey,
125 173 [&](void *ptr, int size) {
126 mdb_dbi_close(env, dbi); 174 const std::string resultValue(static_cast<char*>(ptr), size);
127 mdb_env_close(env); 175 resultHandler(resultValue);
176 });
177// std::cout << "key: " << resultKey << " data: " << resultValue << std::endl;
128} 178}
129 179
130void ReadTransaction::read(const std::string &sKey, const std::function<void(void *data, int size)> &resultHandler) 180void Database::read(const std::string &sKey, const std::function<void(void *ptr, int size)> &resultHandler)
131{ 181{
182 if (!d->env) {
183 return;
184 }
185
132 int rc; 186 int rc;
133 MDB_val key; 187 MDB_val key;
134 MDB_val data; 188 MDB_val data;
@@ -137,6 +191,8 @@ void ReadTransaction::read(const std::string &sKey, const std::function<void(voi
137 key.mv_size = sKey.size(); 191 key.mv_size = sKey.size();
138 key.mv_data = (void*)sKey.data(); 192 key.mv_data = (void*)sKey.data();
139 193
194 /*
195TODO: do we need a write transaction before a read transaction? only relevant for implicitTransactions in any case
140 { 196 {
141 //A write transaction is at least required the first time 197 //A write transaction is at least required the first time
142 rc = mdb_txn_begin(env, nullptr, 0, &txn); 198 rc = mdb_txn_begin(env, nullptr, 0, &txn);
@@ -145,18 +201,27 @@ void ReadTransaction::read(const std::string &sKey, const std::function<void(voi
145 rc = mdb_dbi_open(txn, nullptr, 0, &dbi); 201 rc = mdb_dbi_open(txn, nullptr, 0, &dbi);
146 mdb_txn_abort(txn); 202 mdb_txn_abort(txn);
147 } 203 }
204 */
205 const bool implicitTransaction = !d->transaction;
206 if (implicitTransaction) {
207 // TODO: if this fails, still try the write below?
208 if (!startTransaction(ReadOnly)) {
209 return;
210 }
211 }
148 212
149 rc = mdb_txn_begin(env, nullptr, MDB_RDONLY, &txn); 213 rc = mdb_cursor_open(d->transaction, d->dbi, &cursor);
150 rc = mdb_cursor_open(txn, dbi, &cursor);
151 if (rc) { 214 if (rc) {
152 std::cerr << "mdb_cursor_open: " << rc << mdb_strerror(rc) << std::endl; 215 std::cerr << "mdb_cursor_get: " << rc << mdb_strerror(rc) << std::endl;
216 return;
153 } 217 }
218
154 if (sKey.empty()) { 219 if (sKey.empty()) {
155 std::cout << "Iterating over all values of store!" << std::endl; 220 std::cout << "Iterating over all values of store!" << std::endl;
156 rc = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
157 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { 221 while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
158 resultHandler(data.mv_data, data.mv_size); 222 resultHandler(key.mv_data, data.mv_size);
159 } 223 }
224
160 //We never find the last value 225 //We never find the last value
161 if (rc == MDB_NOTFOUND) { 226 if (rc == MDB_NOTFOUND) {
162 rc = 0; 227 rc = 0;
@@ -168,10 +233,18 @@ void ReadTransaction::read(const std::string &sKey, const std::function<void(voi
168 std::cout << "couldn't find value " << sKey << std::endl; 233 std::cout << "couldn't find value " << sKey << std::endl;
169 } 234 }
170 } 235 }
236
171 if (rc) { 237 if (rc) {
172 std::cerr << "mdb_cursor_get: " << rc << mdb_strerror(rc) << std::endl; 238 std::cerr << "mdb_cursor_get: " << rc << mdb_strerror(rc) << std::endl;
173 } 239 }
240
174 mdb_cursor_close(cursor); 241 mdb_cursor_close(cursor);
175 //We keep the transaction open since we want to keep the returned values alive 242
243 /**
244 we don't abort the transaction since we need it for reading the values
245 if (implicitTransaction) {
246 abortTransaction();
247 }
248 */
176} 249}
177 250
diff --git a/store/database.h b/store/database.h
index ab398a4..2bf0556 100644
--- a/store/database.h
+++ b/store/database.h
@@ -1,38 +1,25 @@
1#pragma once 1#pragma once
2 2
3#include <lmdb.h>
4#include <string> 3#include <string>
5#include <QString> 4#include <QString>
6 5
7class Database { 6class Database {
8public: 7public:
8 enum TransactionType { ReadOnly, ReadWrite };
9
9 Database(const QString &path); 10 Database(const QString &path);
10 ~Database(); 11 ~Database();
11 MDB_txn *startTransaction(); 12 bool isInTransaction() const;
12 void endTransaction(MDB_txn *transaction); 13 bool startTransaction(TransactionType type = ReadWrite);
13 void write(const std::string &sKey, const std::string &sValue, MDB_txn *transaction); 14 bool commitTransaction();
15 void abortTransaction();
16 bool write(const std::string &sKey, const std::string &sValue);
14 //Perhaps prefer iterators (assuming we need to be able to match multiple values 17 //Perhaps prefer iterators (assuming we need to be able to match multiple values
15 void read(const std::string &sKey, const std::function<void(const std::string)> &); 18 void read(const std::string &sKey, const std::function<void(const std::string &value)> &);
16
17private:
18 MDB_env *env;
19 MDB_dbi dbi;
20};
21
22/*
23 * This opens the db for a single read transaction.
24 *
25 * The lifetime of all read values is tied to this transaction.
26 */
27class ReadTransaction {
28public:
29 ReadTransaction(const QString &path);
30 ~ReadTransaction();
31
32 void read(const std::string &sKey, const std::function<void(void *ptr, int size)> &); 19 void read(const std::string &sKey, const std::function<void(void *ptr, int size)> &);
33 20
34private: 21private:
35 MDB_env *env; 22 class Private;
36 MDB_dbi dbi; 23 Private * const d;
37 MDB_txn *txn;
38}; 24};
25
diff --git a/store/test/storagebenchmark.cpp b/store/test/storagebenchmark.cpp
index 6fc50f6..6f9f18b 100644
--- a/store/test/storagebenchmark.cpp
+++ b/store/test/storagebenchmark.cpp
@@ -1,12 +1,14 @@
1#include <QtTest> 1#include <QtTest>
2 2
3#include "calendar_generated.h" 3#include "calendar_generated.h"
4
4#include <iostream> 5#include <iostream>
5#include <fstream> 6#include <fstream>
7
8#include <QDebug>
6#include <QDir> 9#include <QDir>
7#include <QString> 10#include <QString>
8#include <QTime> 11#include <QTime>
9#include <qdebug.h>
10 12
11#include "store/database.h" 13#include "store/database.h"
12 14
@@ -75,7 +77,10 @@ private Q_SLOTS:
75 QFETCH(bool, useDb); 77 QFETCH(bool, useDb);
76 QFETCH(int, count); 78 QFETCH(int, count);
77 79
78 Database db(dbPath); 80 Database *db = 0;
81 if (useDb) {
82 db = new Database(dbPath);
83 }
79 84
80 std::ofstream myfile; 85 std::ofstream myfile;
81 myfile.open(filePath.toStdString()); 86 myfile.open(filePath.toStdString());
@@ -85,21 +90,22 @@ private Q_SLOTS:
85 90
86 time.start(); 91 time.start();
87 { 92 {
88 auto transaction = db.startTransaction();
89 auto event = createEvent(); 93 auto event = createEvent();
90 for (int i = 0; i < count; i++) { 94 for (int i = 0; i < count; i++) {
91 if (useDb && i > 0 && (i % 10000 == 0)) { 95 if (db) {
92 db.endTransaction(transaction); 96 if (i % 10000 == 0) {
93 transaction = db.startTransaction(); 97 db->commitTransaction();
94 } 98 db->startTransaction();
95 if (useDb) { 99 }
96 db.write(keyPrefix + std::to_string(i), event, transaction); 100
101 db->write(keyPrefix + std::to_string(i), event);
97 } else { 102 } else {
98 myfile << event; 103 myfile << event;
99 } 104 }
100 } 105 }
101 if (useDb) { 106
102 db.endTransaction(transaction); 107 if (db) {
108 db->commitTransaction();
103 } else { 109 } else {
104 myfile.close(); 110 myfile.close();
105 } 111 }
@@ -110,8 +116,8 @@ private Q_SLOTS:
110 time.start(); 116 time.start();
111 { 117 {
112 for (int i = 0; i < count; i++) { 118 for (int i = 0; i < count; i++) {
113 if (useDb) { 119 if (db) {
114 db.read(keyPrefix + std::to_string(i)); 120 db->read(keyPrefix + std::to_string(i), [](void *ptr, int size){});
115 } 121 }
116 } 122 }
117 } 123 }