diff --git a/src/cursor.h b/src/cursor.h index 36ea7fa..c9f46aa 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -53,23 +53,36 @@ public: std::pair prev() const; std::pair current() const; - void first(std::pair& out) const; - void last(std::pair& out) const; - void next(std::pair& out) const; - void prev(std::pair& out) const; - void current(std::pair& out) const; + void first(K& key, V& value) const; + void last(K& key, V& value) const; + void next(K& key, V& value) const; + void prev(K& key, V& value) const; + void current(K& key, V& value) const; private: void terminated() const; + void operateCursorRead(K& key, V& value, MDB_cursor_op operation, const std::string& methodName, const std::string& operationName) const; private: Storage* storage; mutable MDB_cursor* cursor; mutable State state; - inline static const std::string openRecordMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/ - inline static const std::string closeRecordMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/ - inline static const std::string renewRecordMethodName = "Cursor::renew"; /**<\brief member function name, just for exceptions*/ + inline static const std::string openCursorMethodName = "Cursor::open"; /**<\brief member function name, just for exceptions*/ + inline static const std::string closeCursorMethodName = "Cursor::close"; /**<\brief member function name, just for exceptions*/ + inline static const std::string renewCursorMethodName = "Cursor::renew"; /**<\brief member function name, just for exceptions*/ + + inline static const std::string firstMethodName = "first"; /**<\brief member function name, just for exceptions*/ + inline static const std::string lastMethodName = "last"; /**<\brief member function name, just for exceptions*/ + inline static const std::string nextMethodName = "next"; /**<\brief member function name, just for exceptions*/ + inline static const std::string prevMethodName = "prev"; /**<\brief member function name, just for exceptions*/ + inline static const std::string currentMethodName = "current"; /**<\brief member function name, just for exceptions*/ + + inline static const std::string firstOperationName = "Cursor::first"; /**<\brief member function name, just for exceptions*/ + inline static const std::string lastOperationName = "Cursor::last"; /**<\brief member function name, just for exceptions*/ + inline static const std::string nextOperationName = "Cursor::next"; /**<\brief member function name, just for exceptions*/ + inline static const std::string prevOperationName = "Cursor::prev"; /**<\brief member function name, just for exceptions*/ + inline static const std::string currentOperationName = "Cursor::current"; /**<\brief member function name, just for exceptions*/ }; }; diff --git a/src/cursor.hpp b/src/cursor.hpp index 6a575ad..57d3a42 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -42,7 +42,7 @@ void LMDBAL::Cursor::terminated () const { template void LMDBAL::Cursor::open () const { - storage->ensureOpened(openRecordMethodName); + storage->ensureOpened(openCursorMethodName); switch (state) { case closed: { TransactionID txn = storage->beginReadOnlyTransaction(); @@ -60,7 +60,7 @@ void LMDBAL::Cursor::open () const { template void LMDBAL::Cursor::open (TransactionID txn) const { - storage->ensureOpened(openRecordMethodName); + storage->ensureOpened(openCursorMethodName); switch (state) { case closed: { int result = mdb_cursor_open(txn, storage->dbi, &cursor); @@ -76,7 +76,7 @@ void LMDBAL::Cursor::open (TransactionID txn) const { template void LMDBAL::Cursor::renew () const { - storage->ensureOpened(openRecordMethodName); + storage->ensureOpened(renewCursorMethodName); switch (state) { case openedPrivate: { TransactionID txn = mdb_cursor_txn(cursor); @@ -100,7 +100,7 @@ void LMDBAL::Cursor::renew () const { template void LMDBAL::Cursor::renew (TransactionID txn) const { - storage->ensureOpened(openRecordMethodName); + storage->ensureOpened(renewCursorMethodName); switch (state) { case openedPrivate: { TransactionID txn = mdb_cursor_txn(cursor); @@ -141,4 +141,90 @@ void LMDBAL::Cursor::close () const { } } +template +void LMDBAL::Cursor::first (K& key, V& value) const { + operateCursorRead(key, value, MDB_FIRST, firstMethodName, firstOperationName); +} + +template +void LMDBAL::Cursor::last (K& key, V& value) const { + operateCursorRead(key, value, MDB_LAST, lastMethodName, lastOperationName); +} + +template +void LMDBAL::Cursor::next (K& key, V& value) const { + operateCursorRead(key, value, MDB_NEXT, nextMethodName, nextOperationName); +} + +template +void LMDBAL::Cursor::prev (K& key, V& value) const { + operateCursorRead(key, value, MDB_PREV, prevMethodName, prevOperationName); +} + +template +void LMDBAL::Cursor::current (K& key, V& value) const { + operateCursorRead(key, value, MDB_GET_CURRENT, currentMethodName, currentOperationName); +} + +template +std::pair LMDBAL::Cursor::first () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_FIRST, firstMethodName, firstOperationName); + return result; +} + +template +std::pair LMDBAL::Cursor::last () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_LAST, lastMethodName, lastOperationName); + return result; +} + +template +std::pair LMDBAL::Cursor::next () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_NEXT, nextMethodName, nextOperationName); + return result; +} + +template +std::pair LMDBAL::Cursor::prev () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_PREV, prevMethodName, prevOperationName); + return result; +} + +template +std::pair LMDBAL::Cursor::current () const { + std::pair result; + operateCursorRead(result.first, result.second, MDB_GET_CURRENT, currentMethodName, currentOperationName); + return result; +} + + +template +void LMDBAL::Cursor::operateCursorRead( + K& key, + V& value, + MDB_cursor_op operation, + const std::string& methodName, + const std::string& operationName +) const { + if (state == closed) + storage->throwCursorNotReady(methodName); + + MDB_val mdbKey, mdbValue; + int result = mdb_cursor_get(cursor, &mdbKey, &mdbValue, operation); + if (result != MDB_SUCCESS) + storage->throwNotFoundOrUnknown(result, operationName); + + storage->keySerializer.deserialize(mdbKey, key); + storage->valueSerializer.deserialize(mdbValue, value); + + if (state == openedPrivate) + storage->discoveredRecord(key, value); + else + storage->discoveredRecord(key, value, mdb_cursor_txn(cursor)); +} + #endif //LMDBAL_CURSOR_HPP diff --git a/src/exceptions.cpp b/src/exceptions.cpp index c7491aa..b401511 100644 --- a/src/exceptions.cpp +++ b/src/exceptions.cpp @@ -55,6 +55,23 @@ std::string LMDBAL::Closed::getMessage() const { return msg; } +LMDBAL::CursorNotReady::CursorNotReady( + const std::string& p_operation, + const std::string& p_dbName, + const std::string& p_tableName +): + Exception(), + operation(p_operation), + dbName(p_dbName), + tableName(p_tableName) {} + +std::string LMDBAL::CursorNotReady::getMessage() const { + std::string msg = "An attempt to perform operation " + operation + + " on closed cursor that belongs to the table " + tableName + + " within database " + dbName; + return msg; +} + LMDBAL::Opened::Opened(const std::string& p_dbName, const std::string& p_action): Exception(), dbName(p_dbName), diff --git a/src/exceptions.h b/src/exceptions.h index c583c9b..8a90690 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -76,6 +76,27 @@ private: std::optional tableName; }; +/** + * \brief Thrown if the cursor was operated in closed state + */ +class CursorNotReady : public Exception { +public: + /** + * \brief Creates exception + * + * \param operation - text name of the method that was called on closed cursor + * \param dbName - name of the database + * \param tableName - name of the storage owning the cursor + */ + CursorNotReady(const std::string& operation, const std::string& dbName, const std::string& tableName); + + std::string getMessage() const; +private: + std::string operation; + std::string dbName; + std::string tableName; +}; + /** * \brief Thrown if something in the database was called on opened state and it is not supported */ diff --git a/src/storage.cpp b/src/storage.cpp index 88a1f1f..124dab8 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -277,6 +277,18 @@ void LMDBAL::iStorage::throwDuplicate(const std::string& key) const { void LMDBAL::iStorage::throwNotFound(const std::string& key) const { throw NotFound(key, db->name, name);} +/** + * \brief Throws LMDBAL::CursorNotReady + * + * Helper function ment to be used in heirs and reduce the code a bit + * + * \param[in] method - called cursor method name, just to show in std::exception::what() message + * + * \exception LMDBAL::CursorNotReady thrown everytime + */ +void LMDBAL::iStorage::throwCursorNotReady(const std::string& method) const { + throw CursorNotReady(method, db->name, name);} + /** * \brief Begins read-only transaction * diff --git a/src/storage.h b/src/storage.h index bfac49c..0a7d645 100644 --- a/src/storage.h +++ b/src/storage.h @@ -50,6 +50,7 @@ protected: void throwUnknown(int rc) const; void throwDuplicate(const std::string& key) const; void throwNotFound(const std::string& key) const; + void throwCursorNotReady(const std::string& method) const; TransactionID beginReadOnlyTransaction() const; TransactionID beginTransaction() const; @@ -99,6 +100,9 @@ protected: Storage(const std::string& name, Base* parent); ~Storage() override; + virtual void discoveredRecord(const K& key, const V& value) const; + virtual void discoveredRecord(const K& key, const V& value, TransactionID txn) const; + public: using iStorage::drop; virtual void addRecord(const K& key, const V& value); diff --git a/src/storage.hpp b/src/storage.hpp index 58e3dee..cd83f5a 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -22,6 +22,8 @@ #include "storage.h" #include "exceptions.h" +#define UNUSED(x) (void)(x) + /** * \class LMDBAL::Storage * \brief This is a basic key value storage. @@ -718,6 +720,19 @@ void LMDBAL::Storage::destroyCursor(Cursor* cursor) { delete cursor; } +template +void LMDBAL::Storage::discoveredRecord(const K& key, const V& value) const { + UNUSED(key); + UNUSED(value); +} + +template +void LMDBAL::Storage::discoveredRecord(const K& key, const V& value, TransactionID txn) const { + UNUSED(key); + UNUSED(value); + UNUSED(txn); +} + /** * \brief A functiion to actually open MDB_dbi storage * diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 43480dd..8e51951 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,6 +7,7 @@ add_executable(runUnitTests serialization.cpp storagetransaction.cpp cachetransaction.cpp + storagecursor.cpp ) target_compile_options(runUnitTests PRIVATE -fPIC) diff --git a/test/storagecursor.cpp b/test/storagecursor.cpp new file mode 100644 index 0000000..e7b1f8d --- /dev/null +++ b/test/storagecursor.cpp @@ -0,0 +1,167 @@ +#include + +#include "base.h" +#include "storage.h" +#include "cursor.h" + +class StorageCursorTest : public ::testing::Test { +protected: + StorageCursorTest(): + ::testing::Test(), + table (db->getStorage("table1")) {} + + ~StorageCursorTest() {} + + static void SetUpTestSuite() { + if (db == nullptr) { + db = new LMDBAL::Base("testBase"); + db->addStorage("table1"); + db->open(); + } + } + + static void TearDownTestSuite() { + db->close(); + db->removeDirectory(); + delete db; + db = nullptr; + } + + static LMDBAL::Base* db; + static LMDBAL::Cursor* cursor; + + LMDBAL::Storage* table; +}; + +LMDBAL::Base* StorageCursorTest::db = nullptr; +LMDBAL::Cursor* StorageCursorTest::cursor = nullptr; + +static const std::map data({ + {245665783, "bothering nerds"}, + {3458, "resilent pick forefront"}, + {105190, "apportunity legal bat"}, + {6510, "outside"}, + {7438537, "damocles plush apparently rusty"}, + {19373572, "local guidence"}, + {138842, "forgetting tusks prepare"}, + {981874, "butchered soaking pawn"}, + {19302, "tanned inmate"}, + {178239, "custody speaks neurotic"}, +}); + +TEST_F(StorageCursorTest, PopulatingTheTable) { + + uint32_t amount = table->addRecords(data); + EXPECT_EQ(amount, data.size()); +} + +TEST_F(StorageCursorTest, Creation) { + cursor = table->createCursor(); + + EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); + + cursor->open(); +} + +TEST_F(StorageCursorTest, FirstPrivate) { + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, NextPrivate) { + std::map::const_iterator reference = data.begin(); + + reference++; + for (; reference != data.end(); ++reference) { + std::pair element = cursor->next(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + } + + EXPECT_THROW(cursor->next(), LMDBAL::NotFound); + + std::pair element = cursor->first(); + reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, LastPrivate) { + std::pair element = cursor->last(); + std::map::const_reverse_iterator reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, PrevPrivate) { + std::map::const_reverse_iterator reference = data.rbegin(); + + reference++; + for (; reference != data.rend(); ++reference) { + std::pair element = cursor->prev(); + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + } + + EXPECT_THROW(cursor->prev(), LMDBAL::NotFound); + + std::pair element = cursor->last(); + reference = data.rbegin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +} + +TEST_F(StorageCursorTest, Destruction) { + cursor->close(); + + EXPECT_THROW(cursor->first(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->last(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->next(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->prev(), LMDBAL::CursorNotReady); + EXPECT_THROW(cursor->current(), LMDBAL::CursorNotReady); +} + +TEST_F(StorageCursorTest, CurrentPrivate) { + cursor->open(); + + EXPECT_THROW(cursor->current(), LMDBAL::Unknown); //yeah, nice thing to write in the doc + + std::pair element = cursor->first(); + std::map::const_iterator reference = data.begin(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + element = cursor->current(); + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + element = cursor->current(); + ++reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); + + cursor->next(); + cursor->next(); + cursor->prev(); + element = cursor->current(); + ++reference; + ++reference; + --reference; + + EXPECT_EQ(element.first, reference->first); + EXPECT_EQ(element.second, reference->second); +}