diff options
author | Minijackson <minijackson@riseup.net> | 2018-05-23 17:20:34 +0200 |
---|---|---|
committer | Minijackson <minijackson@riseup.net> | 2018-05-23 17:25:18 +0200 |
commit | 427b1d38d870e6876e10349b1081b56ff3390dec (patch) | |
tree | 8895888f5e4900ab8b41e500df32a89dda477e1c | |
parent | 426392f71d5f45aad8c57969643fd6c365ce2362 (diff) | |
download | sink-427b1d38d870e6876e10349b1081b56ff3390dec.tar.gz sink-427b1d38d870e6876e10349b1081b56ff3390dec.zip |
Add and test findAllInRange for ranged lookups
-rw-r--r-- | common/storage.h | 5 | ||||
-rw-r--r-- | common/storage_lmdb.cpp | 62 | ||||
-rw-r--r-- | tests/storagetest.cpp | 76 |
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 | ||
561 | int 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, ¤tKey, &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, ¤tKey, &reference, MDB_NEXT) == MDB_SUCCESS); | ||
621 | } | ||
622 | |||
561 | qint64 DataStore::NamedDatabase::getSize() | 623 | qint64 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) { |