diff options
-rw-r--r-- | common/storage.h | 11 | ||||
-rw-r--r-- | common/storage_common.cpp | 11 | ||||
-rw-r--r-- | common/storage_lmdb.cpp | 47 | ||||
-rw-r--r-- | tests/storagetest.cpp | 51 |
4 files changed, 109 insertions, 11 deletions
diff --git a/common/storage.h b/common/storage.h index d186b2e..98b12ed 100644 --- a/common/storage.h +++ b/common/storage.h | |||
@@ -76,9 +76,13 @@ public: | |||
76 | * | 76 | * |
77 | * @return The number of values retrieved. | 77 | * @return The number of values retrieved. |
78 | */ | 78 | */ |
79 | int scan(const QByteArray &k, | 79 | int scan(const QByteArray &key, |
80 | const std::function<bool(const QByteArray &key, const QByteArray &value)> &resultHandler, | 80 | const std::function<bool(const QByteArray &key, const QByteArray &value)> &resultHandler, |
81 | const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>()) const; | 81 | const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>(), bool findSubstringKeys = false) const; |
82 | |||
83 | void findLatest(const QByteArray &uid, | ||
84 | const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler, | ||
85 | const std::function<void(const Storage::Error &error)> &errorHandler = std::function<void(const Storage::Error &error)>()) const; | ||
82 | 86 | ||
83 | NamedDatabase(NamedDatabase&& other) : d(other.d) | 87 | NamedDatabase(NamedDatabase&& other) : d(other.d) |
84 | { | 88 | { |
@@ -170,6 +174,9 @@ public: | |||
170 | static bool isInternalKey(void *key, int keySize); | 174 | static bool isInternalKey(void *key, int keySize); |
171 | static bool isInternalKey(const QByteArray &key); | 175 | static bool isInternalKey(const QByteArray &key); |
172 | 176 | ||
177 | static QByteArray assembleKey(const QByteArray &key, qint64 revision); | ||
178 | static QByteArray uidFromKey(const QByteArray &key); | ||
179 | |||
173 | private: | 180 | private: |
174 | std::function<void(const Storage::Error &error)> mErrorHandler; | 181 | std::function<void(const Storage::Error &error)> mErrorHandler; |
175 | 182 | ||
diff --git a/common/storage_common.cpp b/common/storage_common.cpp index f22150a..28fb4c2 100644 --- a/common/storage_common.cpp +++ b/common/storage_common.cpp | |||
@@ -93,4 +93,15 @@ bool Storage::isInternalKey(const QByteArray &key) | |||
93 | return key.startsWith(s_internalPrefix); | 93 | return key.startsWith(s_internalPrefix); |
94 | } | 94 | } |
95 | 95 | ||
96 | QByteArray Storage::assembleKey(const QByteArray &key, qint64 revision) | ||
97 | { | ||
98 | Q_ASSERT(key.size() == 38); | ||
99 | return key + QByteArray::number(revision); | ||
100 | } | ||
101 | |||
102 | QByteArray Storage::uidFromKey(const QByteArray &key) | ||
103 | { | ||
104 | return key.mid(0, 38); | ||
105 | } | ||
106 | |||
96 | } // namespace Akonadi2 | 107 | } // namespace Akonadi2 |
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp index a048a71..3073d37 100644 --- a/common/storage_lmdb.cpp +++ b/common/storage_lmdb.cpp | |||
@@ -169,7 +169,8 @@ void Storage::NamedDatabase::remove(const QByteArray &k, | |||
169 | 169 | ||
170 | int Storage::NamedDatabase::scan(const QByteArray &k, | 170 | int Storage::NamedDatabase::scan(const QByteArray &k, |
171 | const std::function<bool(const QByteArray &key, const QByteArray &value)> &resultHandler, | 171 | const std::function<bool(const QByteArray &key, const QByteArray &value)> &resultHandler, |
172 | const std::function<void(const Storage::Error &error)> &errorHandler) const | 172 | const std::function<void(const Storage::Error &error)> &errorHandler, |
173 | bool findSubstringKeys) const | ||
173 | { | 174 | { |
174 | if (!d || !d->transaction) { | 175 | if (!d || !d->transaction) { |
175 | //Not an error. We rely on this to read nothing from non-existing databases. | 176 | //Not an error. We rely on this to read nothing from non-existing databases. |
@@ -193,14 +194,25 @@ int Storage::NamedDatabase::scan(const QByteArray &k, | |||
193 | 194 | ||
194 | int numberOfRetrievedValues = 0; | 195 | int numberOfRetrievedValues = 0; |
195 | 196 | ||
196 | if (k.isEmpty() || d->allowDuplicates) { | 197 | if (k.isEmpty() || d->allowDuplicates || findSubstringKeys) { |
197 | if ((rc = mdb_cursor_get(cursor, &key, &data, d->allowDuplicates ? MDB_SET : MDB_FIRST)) == 0) { | 198 | MDB_cursor_op op = d->allowDuplicates ? MDB_SET : MDB_FIRST; |
198 | numberOfRetrievedValues++; | 199 | if (findSubstringKeys) { |
199 | if (resultHandler(QByteArray::fromRawData((char*)key.mv_data, key.mv_size), QByteArray::fromRawData((char*)data.mv_data, data.mv_size))) { | 200 | op = MDB_SET_RANGE; |
200 | while ((rc = mdb_cursor_get(cursor, &key, &data, d->allowDuplicates ? MDB_NEXT_DUP : MDB_NEXT)) == 0) { | 201 | } |
201 | numberOfRetrievedValues++; | 202 | if ((rc = mdb_cursor_get(cursor, &key, &data, op)) == 0) { |
202 | if (!resultHandler(QByteArray::fromRawData((char*)key.mv_data, key.mv_size), QByteArray::fromRawData((char*)data.mv_data, data.mv_size))) { | 203 | //The first lookup will find a key that is equal or greather than our key |
203 | break; | 204 | if (QByteArray::fromRawData((char*)key.mv_data, key.mv_size).startsWith(k)) { |
205 | numberOfRetrievedValues++; | ||
206 | if (resultHandler(QByteArray::fromRawData((char*)key.mv_data, key.mv_size), QByteArray::fromRawData((char*)data.mv_data, data.mv_size))) { | ||
207 | MDB_cursor_op nextOp = d->allowDuplicates ? MDB_NEXT_DUP : MDB_NEXT; | ||
208 | while ((rc = mdb_cursor_get(cursor, &key, &data, nextOp)) == 0) { | ||
209 | //Every consequent lookup simply iterates through the list | ||
210 | if (QByteArray::fromRawData((char*)key.mv_data, key.mv_size).startsWith(k)) { | ||
211 | numberOfRetrievedValues++; | ||
212 | if (!resultHandler(QByteArray::fromRawData((char*)key.mv_data, key.mv_size), QByteArray::fromRawData((char*)data.mv_data, data.mv_size))) { | ||
213 | break; | ||
214 | } | ||
215 | } | ||
204 | } | 216 | } |
205 | } | 217 | } |
206 | } | 218 | } |
@@ -226,6 +238,23 @@ int Storage::NamedDatabase::scan(const QByteArray &k, | |||
226 | 238 | ||
227 | return numberOfRetrievedValues; | 239 | return numberOfRetrievedValues; |
228 | } | 240 | } |
241 | void Storage::NamedDatabase::findLatest(const QByteArray &uid, | ||
242 | const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler, | ||
243 | const std::function<void(const Storage::Error &error)> &errorHandler) const | ||
244 | { | ||
245 | QByteArray latestKey; | ||
246 | scan(uid, [&](const QByteArray &key, const QByteArray &value) -> bool { | ||
247 | latestKey = key; | ||
248 | return true; | ||
249 | }, | ||
250 | errorHandler, true); | ||
251 | |||
252 | scan(latestKey, [=](const QByteArray &key, const QByteArray &value) -> bool { | ||
253 | resultHandler(key, value); | ||
254 | return false; | ||
255 | }, | ||
256 | errorHandler); | ||
257 | } | ||
229 | 258 | ||
230 | 259 | ||
231 | 260 | ||
diff --git a/tests/storagetest.cpp b/tests/storagetest.cpp index 7060ef2..8d5ee00 100644 --- a/tests/storagetest.cpp +++ b/tests/storagetest.cpp | |||
@@ -317,6 +317,41 @@ private Q_SLOTS: | |||
317 | QCOMPARE(numValues, 1); | 317 | QCOMPARE(numValues, 1); |
318 | } | 318 | } |
319 | 319 | ||
320 | void testFindSubstringKeys() | ||
321 | { | ||
322 | Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite); | ||
323 | auto transaction = store.createTransaction(Akonadi2::Storage::ReadWrite); | ||
324 | auto db = transaction.openDatabase("test", nullptr, false); | ||
325 | db.write("sub","value1"); | ||
326 | db.write("subsub","value2"); | ||
327 | db.write("wubsub","value3"); | ||
328 | int numValues = db.scan("sub", [&](const QByteArray &key, const QByteArray &value) -> bool { | ||
329 | return true; | ||
330 | }, nullptr, true); | ||
331 | |||
332 | QCOMPARE(numValues, 2); | ||
333 | } | ||
334 | |||
335 | void testKeySorting() | ||
336 | { | ||
337 | Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite); | ||
338 | auto transaction = store.createTransaction(Akonadi2::Storage::ReadWrite); | ||
339 | auto db = transaction.openDatabase("test", nullptr, false); | ||
340 | db.write("sub_2","value2"); | ||
341 | db.write("sub_1","value1"); | ||
342 | db.write("sub_3","value3"); | ||
343 | QList<QByteArray> results; | ||
344 | int numValues = db.scan("sub", [&](const QByteArray &key, const QByteArray &value) -> bool { | ||
345 | results << value; | ||
346 | return true; | ||
347 | }, nullptr, true); | ||
348 | |||
349 | QCOMPARE(numValues, 3); | ||
350 | QCOMPARE(results.at(0), QByteArray("value1")); | ||
351 | QCOMPARE(results.at(1), QByteArray("value2")); | ||
352 | QCOMPARE(results.at(2), QByteArray("value3")); | ||
353 | } | ||
354 | |||
320 | //Ensure we don't retrieve a key that is greater than the current key. We only want equal keys. | 355 | //Ensure we don't retrieve a key that is greater than the current key. We only want equal keys. |
321 | void testKeyRange() | 356 | void testKeyRange() |
322 | { | 357 | { |
@@ -330,6 +365,22 @@ private Q_SLOTS: | |||
330 | 365 | ||
331 | QCOMPARE(numValues, 0); | 366 | QCOMPARE(numValues, 0); |
332 | } | 367 | } |
368 | |||
369 | void testFindLatest() | ||
370 | { | ||
371 | Akonadi2::Storage store(testDataPath, dbName, Akonadi2::Storage::ReadWrite); | ||
372 | auto transaction = store.createTransaction(Akonadi2::Storage::ReadWrite); | ||
373 | auto db = transaction.openDatabase("test", nullptr, false); | ||
374 | db.write("sub1","value1"); | ||
375 | db.write("sub2","value2"); | ||
376 | db.write("wub3","value3"); | ||
377 | QByteArray result; | ||
378 | db.findLatest("sub", [&](const QByteArray &key, const QByteArray &value) { | ||
379 | result = value; | ||
380 | }); | ||
381 | |||
382 | QCOMPARE(result, QByteArray("value2")); | ||
383 | } | ||
333 | }; | 384 | }; |
334 | 385 | ||
335 | QTEST_MAIN(StorageTest) | 386 | QTEST_MAIN(StorageTest) |