summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/storage.h56
-rw-r--r--common/storage_lmdb.cpp306
-rw-r--r--tests/storagetest.cpp30
3 files changed, 350 insertions, 42 deletions
diff --git a/common/storage.h b/common/storage.h
index 09365b0..77f11f9 100644
--- a/common/storage.h
+++ b/common/storage.h
@@ -51,8 +51,64 @@ public:
51 int code; 51 int code;
52 }; 52 };
53 53
54 class Transaction
55 {
56 public:
57 ~Transaction();
58 bool commit(const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>());
59 void abort();
60
61 /**
62 * Write a value
63 */
64 bool write(const QByteArray &key, const QByteArray &value, const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>());
65
66 /**
67 * Remove a value
68 */
69 void remove(const QByteArray &key,
70 const std::function<void(const Storage::Error &error)> &errorHandler);
71 /**
72 * Read values with a given key.
73 *
74 * * An empty @param key results in a full scan
75 * * If duplicates are existing (revisions), all values are returned.
76 * * The pointers of the returned values are valid during the execution of the @param resultHandler
77 *
78 * @return The number of values retrieved.
79 */
80 int scan(const QByteArray &k,
81 const std::function<bool(const QByteArray &key, const QByteArray &value)> &resultHandler,
82 const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>());
83
84 Transaction(Transaction&& other) : d(other.d)
85 {
86 d = other.d;
87 other.d = nullptr;
88 }
89 Transaction& operator=(Transaction&& other) {
90 d = other.d;
91 other.d = nullptr;
92 return *this;
93 }
94 private:
95 Transaction(Transaction& other);
96 Transaction& operator=(Transaction& other);
97 friend Storage;
98 class Private;
99 Transaction();
100 Transaction(Private*);
101 Private *d;
102 };
103
54 Storage(const QString &storageRoot, const QString &name, AccessMode mode = ReadOnly, bool allowDuplicates = false); 104 Storage(const QString &storageRoot, const QString &name, AccessMode mode = ReadOnly, bool allowDuplicates = false);
55 ~Storage(); 105 ~Storage();
106
107 Transaction createTransaction(AccessMode mode = ReadWrite,
108 const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>());
109
110
111 //Old API
56 bool isInTransaction() const; 112 bool isInTransaction() const;
57 bool startTransaction(AccessMode mode = ReadWrite, const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>()); 113 bool startTransaction(AccessMode mode = ReadWrite, const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>());
58 bool commitTransaction(const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>()); 114 bool commitTransaction(const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>());
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
index 7bbf8b5..230409f 100644
--- a/common/storage_lmdb.cpp
+++ b/common/storage_lmdb.cpp
@@ -47,6 +47,220 @@ int getErrorCode(int e)
47 return -1; 47 return -1;
48} 48}
49 49
50
51
52class Storage::Transaction::Private
53{
54public:
55 Private(MDB_txn *_txn, MDB_dbi _dbi, bool _allowDuplicates, const std::function<void(const Storage::Error &error)> &_defaultErrorHandler, const QString &_name)
56 : transaction(_txn),
57 dbi(_dbi),
58 allowDuplicates(_allowDuplicates),
59 defaultErrorHandler(_defaultErrorHandler),
60 name(_name),
61 implicitCommit(false),
62 error(false)
63 {
64
65 }
66 ~Private()
67 {
68
69 }
70
71 MDB_txn *transaction;
72 MDB_dbi dbi;
73 bool allowDuplicates;
74 std::function<void(const Storage::Error &error)> defaultErrorHandler;
75 QString name;
76 bool implicitCommit;
77 bool error;
78};
79
80Storage::Transaction::Transaction()
81 : d(0)
82{
83
84}
85
86Storage::Transaction::Transaction(Transaction::Private *prv)
87 : d(prv)
88{
89
90}
91
92Storage::Transaction::~Transaction()
93{
94 if (d && d->transaction) {
95 if (d->implicitCommit && !d->error) {
96 commit();
97 } else {
98 mdb_txn_abort(d->transaction);
99 }
100 }
101 delete d;
102}
103
104bool Storage::Transaction::commit(const std::function<void(const Storage::Error &error)> &errorHandler)
105{
106 if (!d) {
107 return false;
108 }
109
110 const int rc = mdb_txn_commit(d->transaction);
111 if (rc) {
112 mdb_txn_abort(d->transaction);
113 Error error(d->name.toLatin1(), ErrorCodes::GenericError, "Error during transaction commit: " + QByteArray(mdb_strerror(rc)));
114 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
115 }
116 d->transaction = nullptr;
117
118 return !rc;
119}
120
121void Storage::Transaction::abort()
122{
123 if (!d || !d->transaction) {
124 return;
125 }
126
127 mdb_txn_abort(d->transaction);
128 d->transaction = nullptr;
129}
130
131bool Storage::Transaction::write(const QByteArray &sKey, const QByteArray &sValue, const std::function<void(const Storage::Error &error)> &errorHandler)
132{
133 if (!d || !d->transaction) {
134 return false;
135 }
136 const void *keyPtr = sKey.data();
137 const size_t keySize = sKey.size();
138 const void *valuePtr = sValue.data();
139 const size_t valueSize = sValue.size();
140
141 if (!keyPtr || keySize == 0) {
142 Error error(d->name.toLatin1(), ErrorCodes::GenericError, "Tried to write empty key.");
143 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
144 return false;
145 }
146
147 int rc;
148 MDB_val key, data;
149 key.mv_size = keySize;
150 key.mv_data = const_cast<void*>(keyPtr);
151 data.mv_size = valueSize;
152 data.mv_data = const_cast<void*>(valuePtr);
153 rc = mdb_put(d->transaction, d->dbi, &key, &data, 0);
154
155 if (rc) {
156 d->error = true;
157 Error error(d->name.toLatin1(), ErrorCodes::GenericError, "mdb_put: " + QByteArray(mdb_strerror(rc)));
158 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
159 } else {
160 d->implicitCommit = true;
161 }
162
163 return !rc;
164}
165
166int Storage::Transaction::scan(const QByteArray &k,
167 const std::function<bool(const QByteArray &key, const QByteArray &value)> &resultHandler,
168 const std::function<void(const Storage::Error &error)> &errorHandler)
169{
170 if (!d || !d->transaction) {
171 Error error(d->name.toLatin1(), ErrorCodes::NotOpen, "Not open");
172 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
173 return 0;
174 }
175
176 int rc;
177 MDB_val key;
178 MDB_val data;
179 MDB_cursor *cursor;
180
181 key.mv_data = (void*)k.constData();
182 key.mv_size = k.size();
183
184 rc = mdb_cursor_open(d->transaction, d->dbi, &cursor);
185 if (rc) {
186 Error error(d->name.toLatin1(), getErrorCode(rc), QByteArray("Error during mdb_cursor open: ") + QByteArray(mdb_strerror(rc)));
187 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
188 return 0;
189 }
190
191 int numberOfRetrievedValues = 0;
192
193 if (k.isEmpty() || d->allowDuplicates) {
194 if ((rc = mdb_cursor_get(cursor, &key, &data, d->allowDuplicates ? MDB_SET_RANGE : MDB_FIRST)) == 0) {
195 numberOfRetrievedValues++;
196 if (resultHandler(QByteArray::fromRawData((char*)key.mv_data, key.mv_size), QByteArray::fromRawData((char*)data.mv_data, data.mv_size))) {
197 while ((rc = mdb_cursor_get(cursor, &key, &data, d->allowDuplicates ? MDB_NEXT_DUP : MDB_NEXT)) == 0) {
198 numberOfRetrievedValues++;
199 if (!resultHandler(QByteArray::fromRawData((char*)key.mv_data, key.mv_size), QByteArray::fromRawData((char*)data.mv_data, data.mv_size))) {
200 break;
201 }
202 }
203 }
204 }
205
206 //We never find the last value
207 if (rc == MDB_NOTFOUND) {
208 rc = 0;
209 }
210 } else {
211 if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_SET)) == 0) {
212 numberOfRetrievedValues++;
213 resultHandler(QByteArray::fromRawData((char*)key.mv_data, key.mv_size), QByteArray::fromRawData((char*)data.mv_data, data.mv_size));
214 }
215 }
216
217 mdb_cursor_close(cursor);
218
219 if (rc) {
220 Error error(d->name.toLatin1(), getErrorCode(rc), QByteArray("Key: ") + k + " : " + QByteArray(mdb_strerror(rc)));
221 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
222 }
223
224 return numberOfRetrievedValues;
225}
226
227void Storage::Transaction::remove(const QByteArray &k,
228 const std::function<void(const Storage::Error &error)> &errorHandler)
229{
230 if (!d) {
231 Error error(d->name.toLatin1(), ErrorCodes::GenericError, "Not open");
232 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
233 return;
234 }
235
236 int rc;
237 MDB_val key;
238 key.mv_size = k.size();
239 key.mv_data = const_cast<void*>(static_cast<const void*>(k.data()));
240 rc = mdb_del(d->transaction, d->dbi, &key, 0);
241
242 if (rc) {
243 d->error = true;
244 Error error(d->name.toLatin1(), ErrorCodes::GenericError, QString("Error on mdb_del: %1 %2").arg(rc).arg(mdb_strerror(rc)).toLatin1());
245 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
246 } else {
247 d->implicitCommit = true;
248 }
249
250 return;
251}
252
253
254
255
256
257
258
259
260
261
262
263
50class Storage::Private 264class Storage::Private
51{ 265{
52public: 266public:
@@ -81,33 +295,38 @@ Storage::Private::Private(const QString &s, const QString &n, AccessMode m, bool
81 allowDuplicates(duplicates) 295 allowDuplicates(duplicates)
82{ 296{
83 const QString fullPath(storageRoot + '/' + name); 297 const QString fullPath(storageRoot + '/' + name);
84 QDir dir; 298 QFileInfo dirInfo(fullPath);
85 dir.mkpath(storageRoot); 299 if (!dirInfo.exists() && mode == ReadWrite) {
86 dir.mkdir(fullPath); 300 QDir().mkpath(fullPath);
87 301 dirInfo.refresh();
88 //Ensure the environment is only created once 302 }
89 QMutexLocker locker(&sMutex); 303 if (mode == ReadWrite && !dirInfo.permission(QFile::WriteOwner)) {
90 304 qCritical() << fullPath << "does not have write permissions. Aborting";
91 int rc = 0; 305 } else if (dirInfo.exists()) {
92 /* 306 //Ensure the environment is only created once
93 * It seems we can only ever have one environment open in the process. 307 QMutexLocker locker(&sMutex);
94 * Otherwise multi-threading breaks. 308
95 */ 309 /*
96 env = sEnvironments.value(fullPath); 310 * It seems we can only ever have one environment open in the process.
97 if (!env) { 311 * Otherwise multi-threading breaks.
98 if ((rc = mdb_env_create(&env))) { 312 */
99 // TODO: handle error 313 env = sEnvironments.value(fullPath);
100 std::cerr << "mdb_env_create: " << rc << " " << mdb_strerror(rc) << std::endl; 314 if (!env) {
101 } else { 315 int rc = 0;
102 if ((rc = mdb_env_open(env, fullPath.toStdString().data(), mode == ReadOnly ? MDB_RDONLY : 0 , 0664))) { 316 if ((rc = mdb_env_create(&env))) {
103 std::cerr << "mdb_env_open: " << rc << " " << mdb_strerror(rc) << std::endl; 317 // TODO: handle error
104 mdb_env_close(env); 318 std::cerr << "mdb_env_create: " << rc << " " << mdb_strerror(rc) << std::endl;
105 env = 0;
106 } else { 319 } else {
107 //FIXME: dynamic resize 320 if ((rc = mdb_env_open(env, fullPath.toStdString().data(), mode == ReadOnly ? MDB_RDONLY : 0 , 0664))) {
108 const size_t dbSize = (size_t)10485760 * (size_t)100 * (size_t)80; //10MB * 800 321 std::cerr << "mdb_env_open: " << rc << " " << mdb_strerror(rc) << std::endl;
109 mdb_env_set_mapsize(env, dbSize); 322 mdb_env_close(env);
110 sEnvironments.insert(fullPath, env); 323 env = 0;
324 } else {
325 //FIXME: dynamic resize
326 const size_t dbSize = (size_t)10485760 * (size_t)8000; //1MB * 8000
327 mdb_env_set_mapsize(env, dbSize);
328 sEnvironments.insert(fullPath, env);
329 }
111 } 330 }
112 } 331 }
113 } 332 }
@@ -142,9 +361,40 @@ bool Storage::exists() const
142 return (d->env != 0); 361 return (d->env != 0);
143} 362}
144 363
145bool Storage::isInTransaction() const 364Storage::Transaction Storage::createTransaction(AccessMode type, const std::function<void(const Storage::Error &error)> &errorHandlerArg)
146{ 365{
147 return d->transaction; 366 auto errorHandler = errorHandlerArg ? errorHandlerArg : defaultErrorHandler();
367 if (!d->env) {
368 errorHandler(Error(d->name.toLatin1(), ErrorCodes::GenericError, "Missing database environment"));
369 return Transaction();
370 }
371
372 bool requestedRead = type == ReadOnly;
373
374 if (d->mode == ReadOnly && !requestedRead) {
375 errorHandler(Error(d->name.toLatin1(), ErrorCodes::GenericError, "Requested read/write transaction in read-only mode."));
376 return Transaction();
377 }
378
379 int rc;
380 MDB_txn *txn;
381 rc = mdb_txn_begin(d->env, NULL, requestedRead ? MDB_RDONLY : 0, &txn);
382 if (!rc) {
383 //TODO: Move opening of dbi into Transaction for different named databases
384 MDB_dbi dbi;
385 rc = mdb_dbi_open(txn, NULL, d->allowDuplicates ? MDB_DUPSORT : 0, &dbi);
386 if (rc) {
387 errorHandler(Error(d->name.toLatin1(), ErrorCodes::GenericError, "Error while opening transaction: " + QByteArray(mdb_strerror(rc))));
388 return Transaction();
389 }
390 return Transaction(new Transaction::Private(txn, dbi, d->allowDuplicates, defaultErrorHandler(), d->name));
391 } else {
392 if (rc) {
393 errorHandler(Error(d->name.toLatin1(), ErrorCodes::GenericError, "Error while beginning transaction: " + QByteArray(mdb_strerror(rc))));
394 return Transaction();
395 }
396 }
397 return Transaction();
148} 398}
149 399
150bool Storage::startTransaction(AccessMode type, 400bool Storage::startTransaction(AccessMode type,
diff --git a/tests/storagetest.cpp b/tests/storagetest.cpp
index b088670..b71a767 100644
--- a/tests/storagetest.cpp
+++ b/tests/storagetest.cpp
@@ -20,17 +20,18 @@ private:
20 void populate(int count) 20 void populate(int count)
21 { 21 {
22 Akonadi2::Storage storage(testDataPath, dbName, Akonadi2::Storage::ReadWrite); 22 Akonadi2::Storage storage(testDataPath, dbName, Akonadi2::Storage::ReadWrite);
23 auto transaction = storage.createTransaction(Akonadi2::Storage::ReadWrite);
23 for (int i = 0; i < count; i++) { 24 for (int i = 0; i < count; i++) {
24 //This should perhaps become an implementation detail of the db? 25 //This should perhaps become an implementation detail of the db?
25 if (i % 10000 == 0) { 26 if (i % 10000 == 0) {
26 if (i > 0) { 27 if (i > 0) {
27 storage.commitTransaction(); 28 transaction.commit();
29 transaction = std::move(storage.createTransaction(Akonadi2::Storage::ReadWrite));
28 } 30 }
29 storage.startTransaction();
30 } 31 }
31 storage.write(keyPrefix + QByteArray::number(i), keyPrefix + QByteArray::number(i)); 32 transaction.write(keyPrefix + QByteArray::number(i), keyPrefix + QByteArray::number(i));
32 } 33 }
33 storage.commitTransaction(); 34 transaction.commit();
34 } 35 }
35 36
36 bool verify(Akonadi2::Storage &storage, int i) 37 bool verify(Akonadi2::Storage &storage, int i)
@@ -38,8 +39,8 @@ private:
38 bool success = true; 39 bool success = true;
39 bool keyMatch = true; 40 bool keyMatch = true;
40 const auto reference = keyPrefix + QByteArray::number(i); 41 const auto reference = keyPrefix + QByteArray::number(i);
41 storage.scan(keyPrefix + QByteArray::number(i), 42 storage.createTransaction(Akonadi2::Storage::ReadOnly).scan(keyPrefix + QByteArray::number(i),
42 [&keyMatch, &reference](const QByteArray &value) -> bool { 43 [&keyMatch, &reference](const QByteArray &key, const QByteArray &value) -> bool {
43 if (value != reference) { 44 if (value != reference) {
44 qDebug() << "Mismatch while reading"; 45 qDebug() << "Mismatch while reading";
45 keyMatch = false; 46 keyMatch = false;
@@ -102,8 +103,8 @@ private Q_SLOTS:
102 { 103 {
103 int hit = 0; 104 int hit = 0;
104 Akonadi2::Storage store(testDataPath, dbName); 105 Akonadi2::Storage store(testDataPath, dbName);
105 store.scan("", [&](void *keyValue, int keySize, void *dataValue, int dataSize) -> bool { 106 store.createTransaction(Akonadi2::Storage::ReadOnly).scan("", [&](const QByteArray &key, const QByteArray &value) -> bool {
106 if (std::string(static_cast<char*>(keyValue), keySize) == "key50") { 107 if (key == "key50") {
107 hit++; 108 hit++;
108 } 109 }
109 return true; 110 return true;
@@ -116,8 +117,8 @@ private Q_SLOTS:
116 int hit = 0; 117 int hit = 0;
117 bool foundInvalidValue = false; 118 bool foundInvalidValue = false;
118 Akonadi2::Storage store(testDataPath, dbName); 119 Akonadi2::Storage store(testDataPath, dbName);
119 store.scan("key50", [&](void *keyValue, int keySize, void *dataValue, int dataSize) -> bool { 120 store.createTransaction(Akonadi2::Storage::ReadOnly).scan("key50", [&](const QByteArray &key, const QByteArray &value) -> bool {
120 if (std::string(static_cast<char*>(keyValue), keySize) != "key50") { 121 if (key != "key50") {
121 foundInvalidValue = true; 122 foundInvalidValue = true;
122 } 123 }
123 hit++; 124 hit++;
@@ -128,12 +129,13 @@ private Q_SLOTS:
128 } 129 }
129 } 130 }
130 131
131 void testTurnReadToWrite() 132 void testNestedOperations()
132 { 133 {
133 populate(3); 134 populate(3);
134 Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite); 135 Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite);
135 store.scan("key1", [&](void *keyValue, int keySize, void *dataValue, int dataSize) -> bool { 136 auto transaction = store.createTransaction(Akonadi2::Storage::ReadWrite);
136 store.remove(QByteArray::fromRawData(static_cast<const char*>(keyValue), keySize), [](const Akonadi2::Storage::Error &) { 137 transaction.scan("key1", [&](const QByteArray &key, const QByteArray &value) -> bool {
138 transaction.remove(key, [](const Akonadi2::Storage::Error &) {
137 QVERIFY(false); 139 QVERIFY(false);
138 }); 140 });
139 return false; 141 return false;
@@ -145,7 +147,7 @@ private Q_SLOTS:
145 bool gotResult = false; 147 bool gotResult = false;
146 bool gotError = false; 148 bool gotError = false;
147 Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite); 149 Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite);
148 int numValues = store.scan("", [&](void *keyValue, int keySize, void *dataValue, int dataSize) -> bool { 150 int numValues = store.createTransaction(Akonadi2::Storage::ReadOnly).scan("", [&](const QByteArray &key, const QByteArray &value) -> bool {
149 gotResult = true; 151 gotResult = true;
150 return false; 152 return false;
151 }, 153 },