diff options
-rw-r--r-- | common/storage.h | 9 | ||||
-rw-r--r-- | common/storage_lmdb.cpp | 54 | ||||
-rw-r--r-- | tests/storagetest.cpp | 76 |
3 files changed, 139 insertions, 0 deletions
diff --git a/common/storage.h b/common/storage.h index bb8c1fa..a8c486c 100644 --- a/common/storage.h +++ b/common/storage.h | |||
@@ -109,6 +109,15 @@ public: | |||
109 | const std::function<void(const DataStore::Error &error)> &errorHandler = std::function<void(const DataStore::Error &error)>()) const; | 109 | const std::function<void(const DataStore::Error &error)> &errorHandler = std::function<void(const DataStore::Error &error)>()) const; |
110 | 110 | ||
111 | /** | 111 | /** |
112 | * Finds all the keys and values whose keys are in a given range | ||
113 | * (inclusive). | ||
114 | */ | ||
115 | int findAllInRange(const QByteArray &lowerBound, const QByteArray &upperBound, | ||
116 | const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler, | ||
117 | const std::function<void(const DataStore::Error &error)> &errorHandler = | ||
118 | std::function<void(const DataStore::Error &error)>()) const; | ||
119 | |||
120 | /** | ||
112 | * Returns true if the database contains the substring key. | 121 | * Returns true if the database contains the substring key. |
113 | */ | 122 | */ |
114 | bool contains(const QByteArray &uid); | 123 | bool contains(const QByteArray &uid); |
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp index bf631a8..4ac5724 100644 --- a/common/storage_lmdb.cpp +++ b/common/storage_lmdb.cpp | |||
@@ -551,6 +551,60 @@ void DataStore::NamedDatabase::findLatest(const QByteArray &k, const std::functi | |||
551 | return; | 551 | return; |
552 | } | 552 | } |
553 | 553 | ||
554 | int DataStore::NamedDatabase::findAllInRange(const QByteArray &lowerBound, const QByteArray &upperBound, | ||
555 | const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler, | ||
556 | const std::function<void(const DataStore::Error &error)> &errorHandler) const | ||
557 | { | ||
558 | if (!d || !d->transaction) { | ||
559 | // Not an error. We rely on this to read nothing from non-existing databases. | ||
560 | return 0; | ||
561 | } | ||
562 | |||
563 | MDB_cursor *cursor; | ||
564 | if (int rc = mdb_cursor_open(d->transaction, d->dbi, &cursor)) { | ||
565 | // Invalid arguments can mean that the transaction doesn't contain the db dbi | ||
566 | Error error(d->name.toLatin1() + d->db, getErrorCode(rc), | ||
567 | QByteArray("Error during mdb_cursor_open: ") + QByteArray(mdb_strerror(rc)) + | ||
568 | ". Lower bound: " + lowerBound + " Upper bound: " + upperBound); | ||
569 | errorHandler ? errorHandler(error) : d->defaultErrorHandler(error); | ||
570 | return 0; | ||
571 | } | ||
572 | |||
573 | int numberOfRetrievedValues = 0; | ||
574 | |||
575 | MDB_val firstKey = { .mv_size = (size_t)lowerBound.size(), .mv_data = (void *)lowerBound.constData() }; | ||
576 | MDB_val idealLastKey = { .mv_size = (size_t)upperBound.size(), .mv_data = (void *)upperBound.constData() }; | ||
577 | MDB_val lastKey = idealLastKey; | ||
578 | MDB_val currentKey; | ||
579 | MDB_val data; | ||
580 | |||
581 | // Find the first key in the range | ||
582 | int rc = mdb_cursor_get(cursor, &firstKey, &data, MDB_SET_RANGE); | ||
583 | |||
584 | if (rc != MDB_SUCCESS) { | ||
585 | // Nothing is greater or equal than the lower bound, meaning no result | ||
586 | mdb_cursor_close(cursor); | ||
587 | return 0; | ||
588 | } | ||
589 | |||
590 | currentKey = firstKey; | ||
591 | |||
592 | // If already bigger than the upper bound | ||
593 | if (mdb_cmp(d->transaction, d->dbi, ¤tKey, &idealLastKey) > 0) { | ||
594 | mdb_cursor_close(cursor); | ||
595 | return 0; | ||
596 | } | ||
597 | |||
598 | do { | ||
599 | const auto currentBAKey = QByteArray::fromRawData((char *)currentKey.mv_data, currentKey.mv_size); | ||
600 | const auto currentBAValue = QByteArray::fromRawData((char *)data.mv_data, data.mv_size); | ||
601 | resultHandler(currentBAKey, currentBAValue); | ||
602 | } while (mdb_cursor_get(cursor, ¤tKey, &data, MDB_NEXT) == MDB_SUCCESS && | ||
603 | mdb_cmp(d->transaction, d->dbi, ¤tKey, &idealLastKey) <= 0); | ||
604 | |||
605 | mdb_cursor_close(cursor); | ||
606 | } | ||
607 | |||
554 | qint64 DataStore::NamedDatabase::getSize() | 608 | qint64 DataStore::NamedDatabase::getSize() |
555 | { | 609 | { |
556 | if (!d || !d->transaction) { | 610 | if (!d || !d->transaction) { |
diff --git a/tests/storagetest.cpp b/tests/storagetest.cpp index 618f9d0..bca91b1 100644 --- a/tests/storagetest.cpp +++ b/tests/storagetest.cpp | |||
@@ -462,6 +462,82 @@ private slots: | |||
462 | QCOMPARE(result, QByteArray("value2")); | 462 | QCOMPARE(result, QByteArray("value2")); |
463 | } | 463 | } |
464 | 464 | ||
465 | void setupTestFindRange(Sink::Storage::DataStore::NamedDatabase &db) | ||
466 | { | ||
467 | db.write("0002", "value1"); | ||
468 | db.write("0003", "value2"); | ||
469 | db.write("0004", "value3"); | ||
470 | db.write("0005", "value4"); | ||
471 | } | ||
472 | |||
473 | void testFindRangeOptimistic() | ||
474 | { | ||
475 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | ||
476 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | ||
477 | auto db = transaction.openDatabase("test", nullptr, false); | ||
478 | setupTestFindRange(db); | ||
479 | QByteArrayList results; | ||
480 | db.findAllInRange("0002", "0004", [&](const QByteArray &key, const QByteArray &value) { results << value; }); | ||
481 | |||
482 | QCOMPARE(results, (QByteArrayList{"value1", "value2", "value3"})); | ||
483 | } | ||
484 | |||
485 | void testFindRangeNothing() | ||
486 | { | ||
487 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | ||
488 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | ||
489 | auto db = transaction.openDatabase("test", nullptr, false); | ||
490 | setupTestFindRange(db); | ||
491 | |||
492 | QByteArrayList results1; | ||
493 | db.findAllInRange("0000", "0001", [&](const QByteArray &key, const QByteArray &value) { results1 << value; }); | ||
494 | QCOMPARE(results1, QByteArrayList{}); | ||
495 | |||
496 | QByteArrayList results2; | ||
497 | db.findAllInRange("0000", "0000", [&](const QByteArray &key, const QByteArray &value) { results2 << value; }); | ||
498 | QCOMPARE(results2, QByteArrayList{}); | ||
499 | |||
500 | QByteArrayList results3; | ||
501 | db.findAllInRange("0006", "0010", [&](const QByteArray &key, const QByteArray &value) { results3 << value; }); | ||
502 | QCOMPARE(results3, QByteArrayList{}); | ||
503 | |||
504 | QByteArrayList results4; | ||
505 | db.findAllInRange("0010", "0010", [&](const QByteArray &key, const QByteArray &value) { results4 << value; }); | ||
506 | QCOMPARE(results4, QByteArrayList{}); | ||
507 | } | ||
508 | |||
509 | void testFindRangeSingle() | ||
510 | { | ||
511 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | ||
512 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | ||
513 | auto db = transaction.openDatabase("test", nullptr, false); | ||
514 | setupTestFindRange(db); | ||
515 | |||
516 | QByteArrayList results1; | ||
517 | db.findAllInRange("0004", "0004", [&](const QByteArray &key, const QByteArray &value) { results1 << value; }); | ||
518 | QCOMPARE(results1, QByteArrayList{"value3"}); | ||
519 | } | ||
520 | |||
521 | void testFindRangeOutofBounds() | ||
522 | { | ||
523 | Sink::Storage::DataStore store(testDataPath, dbName, Sink::Storage::DataStore::ReadWrite); | ||
524 | auto transaction = store.createTransaction(Sink::Storage::DataStore::ReadWrite); | ||
525 | auto db = transaction.openDatabase("test", nullptr, false); | ||
526 | setupTestFindRange(db); | ||
527 | |||
528 | QByteArrayList results1; | ||
529 | db.findAllInRange("0000", "0010", [&](const QByteArray &key, const QByteArray &value) { results1 << value; }); | ||
530 | QCOMPARE(results1, (QByteArrayList{"value1", "value2", "value3", "value4"})); | ||
531 | |||
532 | QByteArrayList results2; | ||
533 | db.findAllInRange("0003", "0010", [&](const QByteArray &key, const QByteArray &value) { results2 << value; }); | ||
534 | QCOMPARE(results2, (QByteArrayList{"value2", "value3", "value4"})); | ||
535 | |||
536 | QByteArrayList results3; | ||
537 | db.findAllInRange("0000", "0003", [&](const QByteArray &key, const QByteArray &value) { results3 << value; }); | ||
538 | QCOMPARE(results3, (QByteArrayList{"value1", "value2"})); | ||
539 | } | ||
540 | |||
465 | void testTransactionVisibility() | 541 | void testTransactionVisibility() |
466 | { | 542 | { |
467 | auto readValue = [](const Sink::Storage::DataStore::NamedDatabase &db, const QByteArray) { | 543 | auto readValue = [](const Sink::Storage::DataStore::NamedDatabase &db, const QByteArray) { |