summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/storage.h5
-rw-r--r--common/storage_lmdb.cpp62
-rw-r--r--tests/storagetest.cpp76
3 files changed, 143 insertions, 0 deletions
diff --git a/common/storage.h b/common/storage.h
index bb8c1fa..1fde8e6 100644
--- a/common/storage.h
+++ b/common/storage.h
@@ -108,6 +108,11 @@ public:
108 void findLatest(const QByteArray &uid, const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler, 108 void findLatest(const QByteArray &uid, const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler,
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 int findAllInRange(const QByteArray &lowerBound, const QByteArray &upperBound,
112 const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler,
113 const std::function<void(const DataStore::Error &error)> &errorHandler =
114 std::function<void(const DataStore::Error &error)>()) const;
115
111 /** 116 /**
112 * Returns true if the database contains the substring key. 117 * Returns true if the database contains the substring key.
113 */ 118 */
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp
index 5fb1d0f..bd9bdce 100644
--- a/common/storage_lmdb.cpp
+++ b/common/storage_lmdb.cpp
@@ -558,6 +558,68 @@ void DataStore::NamedDatabase::findLatest(const QByteArray &k, const std::functi
558 return; 558 return;
559} 559}
560 560
561int DataStore::NamedDatabase::findAllInRange(const QByteArray &lowerBound, const QByteArray &upperBound,
562 const std::function<void(const QByteArray &key, const QByteArray &value)> &resultHandler,
563 const std::function<void(const DataStore::Error &error)> &errorHandler) const
564{
565 if (!d || !d->transaction) {
566 // Not an error. We rely on this to read nothing from non-existing databases.
567 return 0;
568 }
569
570 MDB_cursor *cursor;
571 if (int rc = mdb_cursor_open(d->transaction, d->dbi, &cursor)) {
572 // Invalid arguments can mean that the transaction doesn't contain the db dbi
573 Error error(d->name.toLatin1() + d->db, getErrorCode(rc),
574 QByteArray("Error during mdb_cursor_open: ") + QByteArray(mdb_strerror(rc)) +
575 ". Lower bound: " + lowerBound + " Upper bound: " + upperBound);
576 errorHandler ? errorHandler(error) : d->defaultErrorHandler(error);
577 return 0;
578 }
579
580 int numberOfRetrievedValues = 0;
581
582 MDB_val firstKey = { .mv_size = (size_t)lowerBound.size(), .mv_data = (void *)lowerBound.constData() },
583 idealLastKey = { .mv_size = (size_t)upperBound.size(), .mv_data = (void *)upperBound.constData() },
584 lastKey = idealLastKey, currentKey;
585 MDB_val reference;
586
587 // Find the last key past the range
588 int rc = mdb_cursor_get(cursor, &lastKey, &reference, MDB_SET_RANGE);
589
590 // If only equal, move forward one spot
591 if (rc == MDB_SUCCESS && mdb_cmp(d->transaction, d->dbi, &lastKey, &idealLastKey) == 0) {
592 rc = mdb_cursor_get(cursor, &lastKey, &reference, MDB_NEXT);
593 }
594
595 bool untilEnd = false;
596 if (rc != MDB_SUCCESS) {
597 // Nothing is greater than the upper bound, meaning search until the end
598 untilEnd = true;
599 }
600
601 // Find the first key in the range
602 rc = mdb_cursor_get(cursor, &firstKey, &reference, MDB_SET_RANGE);
603
604 if (rc != MDB_SUCCESS) {
605 // Nothing is greater or equal than the lower bound, meaning no result
606 return 0;
607 }
608
609 currentKey = firstKey;
610
611 do {
612 // If we have a stopping point and this is the stopping point
613 if (!untilEnd && mdb_cmp(d->transaction, d->dbi, &currentKey, &lastKey) == 0) {
614 break;
615 }
616
617 const auto currentBAKey = QByteArray::fromRawData((char *)currentKey.mv_data, currentKey.mv_size);
618 const auto currentBAValue = QByteArray::fromRawData((char *)reference.mv_data, reference.mv_size);
619 resultHandler(currentBAKey, currentBAValue);
620 } while (mdb_cursor_get(cursor, &currentKey, &reference, MDB_NEXT) == MDB_SUCCESS);
621}
622
561qint64 DataStore::NamedDatabase::getSize() 623qint64 DataStore::NamedDatabase::getSize()
562{ 624{
563 if (!d || !d->transaction) { 625 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) {