From 6d21ecc1552b3f42387f805e3aae97f69337a6b3 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 8 Apr 2023 02:27:53 +0300 Subject: [PATCH] one more test for adding multiple entries, fix of leaking cursor, Storage::change method rework, beginning of documentation --- doc/mainpage.dox | 3 +- src/storage.hpp | 189 ++++++++++++++++++++++++++++++++++++++- test/basic.cpp | 223 +++++++++++++++++++++++++++++++---------------- 3 files changed, 334 insertions(+), 81 deletions(-) diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 95db214..7cd5015 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -25,6 +25,7 @@ * At this point you are not allowed to add any more storages, otherwise LMDBAL::Opened exception will be thrown. * It's currently the limitation of this little library and I might solve it in the future. * Database will throw no exception if you will try to close the closed LMDBAL::Base or open again already opened one. - * Also it will automatically close itself you will try to destoroy onpened LMDBAL::Base. + * Also it will automatically close itself if you'll try to destoroy onpened LMDBAL::Base. * + * To discover how to store read and modify data take a look at LMDBAL::Storage and LMDBAL::Cache classes. */ diff --git a/src/storage.hpp b/src/storage.hpp index 8508268..6818552 100644 --- a/src/storage.hpp +++ b/src/storage.hpp @@ -22,6 +22,20 @@ #include "storage.h" #include "exceptions.h" +/** + * \class LMDBAL::Storage + * \brief This is a basic key value storage. + * + * \tparam K type of the keys of the storage + * \tparam V type of the values of the storage + * + * You can receive an instance of this class calling LMDBAL::Base::addStorage(const std::string&) + * if the database is yet closed and you're defining the storages you're going to need. + * Or you can call LMDBAL::Base::getStorage(const std::string&) if the database is opened and you didn't save a pointer to the storage + * + * You are not supposed to instantiate or destory instances of this class yourself! + */ + template LMDBAL::Storage::Storage(const std::string& p_name, Base* parent): iStorage(p_name, parent), @@ -35,6 +49,18 @@ LMDBAL::Storage::~Storage() { delete keySerializer; } +/** + * \brief Adds a key-value record to the storage + * + * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown + * + * \param[in] key key of the record + * \param[in] value value of the record + * + * \exception LMDBAL::Exist thrown if the storage already has a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::addRecord(const K& key, const V& value) { ensureOpened(addRecordMethodName); @@ -49,6 +75,22 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value) { commitTransaction(txn); } +/** + * \brief Adds a key-value record to the storage (transaction variant) + * + * This function schedules an addition of a key-value record, but doesn't immidiately adds it. + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * + * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown + * + * \param[in] key key of the record + * \param[in] value value of the record + * \param[in] txn transaction ID, needs to be a writable transaction! + * + * \exception LMDBAL::Exist thrown if the storage already has a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(addRecordMethodName); @@ -61,6 +103,15 @@ void LMDBAL::Storage::addRecord(const K& key, const V& value, TransactionI throwDuplicateOrUnknown(rc, toString(key)); } +/** + * \brief Adds a key-value record to the storage, overwrites if it already exists + * \param[in] key key of the record + * \param[in] value value of the record + * \returns true if the record was added, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { ensureOpened(forceRecordMethodName); @@ -78,6 +129,21 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value) { return added; } +/** + * \brief Adds a key-value record to the storage, overwrites if it already exists (transaction variant) + * + * This function schedules an addition of a key-value record, but doesn't immidiately adds it. + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * If the record did already exist in the database the actual overwrite will be done only after calling LMDBAL::Base::commitTransaction(). + * + * \param[in] key key of the record + * \param[in] value value of the record + * \param[in] txn transaction ID, needs to be a writable transaction! + * \returns true if the record was added, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template bool LMDBAL::Storage::forceRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(forceRecordMethodName); @@ -107,6 +173,18 @@ bool LMDBAL::Storage::forceRecord(const K& key, const V& value, Transactio return added; } +/** + * \brief Changes key-value record to the storage. + * + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record + * \param[in] value new value of the record + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::changeRecord(const K& key, const V& value) { ensureOpened(changeRecordMethodName); @@ -122,18 +200,55 @@ void LMDBAL::Storage::changeRecord(const K& key, const V& value) { commitTransaction(txn); } +/** + * \brief Changes key-value record to the storage (transaction variant) + * + * This function schedules a modification of a key-value record, but doesn't immidiately changes it. + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction(). + * + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record + * \param[in] value new value of the record + * \param[in] txn transaction ID, needs to be a writable transaction! + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::changeRecord(const K& key, const V& value, TransactionID txn) { ensureOpened(changeRecordMethodName); - MDB_val lmdbKey = keySerializer->setData(key); - MDB_val lmdbData = valueSerializer->setData(value); + MDB_cursor* cursor; + int rc = mdb_cursor_open(txn, dbi, &cursor); + if (rc != MDB_SUCCESS) + throwUnknown(rc); - int rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0); + MDB_val lmdbKey = keySerializer->setData(key); + rc = mdb_cursor_get(cursor, &lmdbKey, nullptr, MDB_SET); + if (rc != MDB_SUCCESS) + throwNotFoundOrUnknown(rc, toString(key)); + + MDB_val lmdbData = valueSerializer->setData(value); + rc = mdb_cursor_put(cursor, &lmdbKey, &lmdbData, MDB_CURRENT); + mdb_cursor_close(cursor); if (rc != MDB_SUCCESS) throwUnknown(rc); } +/** + * \brief Gets the record from the database + + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record you look for + * \returns the value from the storage + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template V LMDBAL::Storage::getRecord(const K& key) const { ensureOpened(getRecordMethodName); @@ -143,6 +258,18 @@ V LMDBAL::Storage::getRecord(const K& key) const { return value; } +/** + * \brief Gets the record from the database (reference variant) + + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record you look for + * \param[out] value the value from the storage + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::getRecord(const K& key, V& value) const { ensureOpened(getRecordMethodName); @@ -158,6 +285,22 @@ void LMDBAL::Storage::getRecord(const K& key, V& value) const { abortTransaction(txn); } +/** + * \brief Gets the record from the database (transaction variant) + * + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record you look for + * \param[in] txn transaction ID, can be read only transaction + * \returns the value from the storage + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { ensureOpened(getRecordMethodName); @@ -167,6 +310,22 @@ V LMDBAL::Storage::getRecord(const K& key, TransactionID txn) const { return value; } +/** + * \brief Gets the record from the database (transaction, reference variant) + * + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown + * + * \param[in] key key of the record you look for + * \param[out] value the value from the storage + * \param[in] txn transaction ID, can be read only transaction + * + * \exception LMDBAL::NotFound thrown if the storage doesn't have a record with the given key + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) const { ensureOpened(getRecordMethodName); @@ -181,6 +340,15 @@ void LMDBAL::Storage::getRecord(const K& key, V& value, TransactionID txn) valueSerializer->deserialize(lmdbData, value); } +/** + * \brief Chechs if storage has value + * + * \param[in] key key of the record you look for + * \returns true if there was a record with given key, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template bool LMDBAL::Storage::checkRecord(const K& key) const { ensureOpened(checkRecordMethodName); @@ -198,6 +366,19 @@ bool LMDBAL::Storage::checkRecord(const K& key) const { return result; } +/** + * \brief Chechs if storage has value (transaction variant) + * + * You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). + * If you just want to read data you should prefer LMDBAL::Base::beginReadOnlyTransaction(). + * + * \param[in] key key of the record you look for + * \param[in] txn transaction ID, can be read only transaction + * \returns true if there was a record with given key, false otherwise + * + * \exception LMDBAL::Closed thrown if the database was not opened + * \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb + */ template bool LMDBAL::Storage::checkRecord(const K& key, TransactionID txn) const { ensureOpened(checkRecordMethodName); @@ -267,7 +448,7 @@ void LMDBAL::Storage::readAll(std::map& result, TransactionID txn) c valueSerializer->deserialize(lmdbData, value); rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT); } - + mdb_cursor_close(cursor); if (rc != MDB_NOTFOUND) throwUnknown(rc); } diff --git a/test/basic.cpp b/test/basic.cpp index baf48cf..143e2c7 100644 --- a/test/basic.cpp +++ b/test/basic.cpp @@ -83,67 +83,25 @@ TEST_F(BaseTest, AddingKeysToCache) { EXPECT_EQ(c1->getRecord(-116), "whatever"); } -TEST_F(BaseTest, AddingRepeatingIntegerKey) { +TEST_F(BaseTest, AddingRepeatingKey) { EXPECT_EQ(db->ready(), true); - bool thrown = false; - try { - t1->addRecord(3, 24); - } catch (const LMDBAL::Exist e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + + EXPECT_THROW(t1->addRecord(3, 24), LMDBAL::Exist); EXPECT_EQ(t1->getRecord(3), 15); -} -TEST_F(BaseTest, AddingRepeatingStringKey) { - EXPECT_EQ(db->ready(), true); - bool thrown = false; - try { - t2->addRecord("sdfhga", "world"); - } catch (const LMDBAL::Exist e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_THROW(t2->addRecord("sdfhga", "world"), LMDBAL::Exist); EXPECT_EQ(t2->getRecord("sdfhga"), "DSFFDG"); -} -TEST_F(BaseTest, AddingRepeatingCacheKey) { - EXPECT_EQ(db->ready(), true); - bool thrown = false; - try { - c1->addRecord(-4, "world"); - } catch (const LMDBAL::Exist e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_THROW(c1->addRecord(-4, "world"), LMDBAL::Exist); EXPECT_EQ(c1->getRecord(-4), "testing goes brrr"); } TEST_F(BaseTest, GettingNotExistingKeys) { EXPECT_EQ(db->ready(), true); - bool thrown = false; - try { - QString wrong = t2->getRecord("almonds"); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; - thrown = false; - try { - uint32_t wrong = t1->getRecord(64); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; - - thrown = false; - try { - std::string wrong = c1->getRecord(21); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_THROW(t2->getRecord("almonds"), LMDBAL::NotFound); + EXPECT_THROW(t1->getRecord(64), LMDBAL::NotFound); + EXPECT_THROW(c1->getRecord(21), LMDBAL::NotFound); } TEST_F(BaseTest, Persistence) { @@ -172,29 +130,9 @@ TEST_F(BaseTest, Persistence) { EXPECT_EQ(c1->getRecord(-37), "aaaaa tss tsss tsss tsss aaaaaaa"); EXPECT_EQ(c1->getRecord(2), "blah balah"); - bool thrown = false; - try { - QString wrong = t2->getRecord("cats"); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; - - thrown = false; - try { - uint32_t wrong = t1->getRecord(7893); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; - - thrown = false; - try { - std::string wrong = c1->getRecord(89); - } catch (const LMDBAL::NotFound e) { - thrown = true; - } - ASSERT_EQ(thrown, true) << "The expected behaviour is to throw exception on duplicate, but it didn't happened"; + EXPECT_THROW(t2->getRecord("cats"), LMDBAL::NotFound); + EXPECT_THROW(t1->getRecord(7893), LMDBAL::NotFound); + EXPECT_THROW(c1->getRecord(89), LMDBAL::NotFound); } TEST_F(BaseTest, CountAndDrop) { @@ -239,6 +177,10 @@ TEST_F(BaseTest, Change) { c1->changeRecord(15, "recording"); c1->changeRecord(12, "thermal"); + EXPECT_THROW(t1->changeRecord(37, 49), LMDBAL::NotFound); + EXPECT_THROW(t2->changeRecord("precision", "cryoplastics"), LMDBAL::NotFound); + EXPECT_THROW(c1->changeRecord(-62, "sharks"), LMDBAL::NotFound); + EXPECT_EQ(t1->getRecord(2), 49); EXPECT_EQ(t2->getRecord("sdfhga"), "void"); EXPECT_EQ(c1->getRecord(15), "recording"); @@ -302,8 +244,6 @@ TEST_F(BaseTest, ReadAll) { EXPECT_EQ(m3.size(), 4); } - - TEST_F(BaseTest, ReplaceAll) { EXPECT_EQ(db->ready(), true); @@ -346,7 +286,6 @@ TEST_F(BaseTest, ReplaceAll) { EXPECT_EQ(t2->getRecord("cluster"), "throttle"); EXPECT_EQ(t2->getRecord("ronin"), "cheese"); - c1->replaceAll({ {68, "quality"}, {31, "ridgid body"}, @@ -362,3 +301,135 @@ TEST_F(BaseTest, ReplaceAll) { EXPECT_EQ(c1->getRecord(22), "pseudo"); EXPECT_EQ(c1->getRecord(-117), "lance of Michael"); } + + + +TEST_F(BaseTest, AddRecords) { + EXPECT_EQ(db->ready(), true); + + LMDBAL::SizeType s1 = t1->addRecords({ + {5, 3}, + {800, 9} + }); + EXPECT_EQ(s1, 6); + EXPECT_EQ(t1->getRecord(7), 48); + EXPECT_EQ(t1->getRecord(194), 582); + EXPECT_EQ(t1->getRecord(857), 39); + EXPECT_EQ(t1->getRecord(9717), 8); + EXPECT_EQ(t1->getRecord(5), 3); + EXPECT_EQ(t1->getRecord(800), 9); + s1 = t1->addRecords({ + {194, 371}, + {808, 487}, + {807, 0} + }, true); + EXPECT_EQ(s1, 8); + EXPECT_EQ(t1->count(), 8); + EXPECT_EQ(t1->getRecord(7), 48); + EXPECT_EQ(t1->getRecord(194), 371); + EXPECT_EQ(t1->getRecord(857), 39); + EXPECT_EQ(t1->getRecord(9717), 8); + EXPECT_EQ(t1->getRecord(5), 3); + EXPECT_EQ(t1->getRecord(800), 9); + EXPECT_EQ(t1->getRecord(808), 487); + EXPECT_EQ(t1->getRecord(807), 0); + EXPECT_THROW( + s1 = t1->addRecords({ + {194, 371}, + {808, 487}, + {807, 0} + }), LMDBAL::Exist + ); + EXPECT_EQ(t1->count(), 8); + + LMDBAL::SizeType s2 = t2->addRecords({ + {"lama", "not quite"}, + {"by the shadow", "leech"}, + {"summertime", "curses"} + }); + EXPECT_EQ(s2, 6); + EXPECT_EQ(t2->count(), 6); + EXPECT_EQ(t2->getRecord("bringin"), "keyboard"); + EXPECT_EQ(t2->getRecord("cluster"), "throttle"); + EXPECT_EQ(t2->getRecord("ronin"), "cheese"); + EXPECT_EQ(t2->getRecord("lama"), "not quite"); + EXPECT_EQ(t2->getRecord("by the shadow"), "leech"); + EXPECT_EQ(t2->getRecord("summertime"), "curses"); + s2 = t2->addRecords({ + {"worry not", "for shall you"}, + {"by the shadow", "face the inevitable"}, + {"cluster", "sobing over those"} + }, true); + + EXPECT_EQ(s2, 7); + EXPECT_EQ(t2->count(), 7); + EXPECT_EQ(t2->getRecord("bringin"), "keyboard"); + EXPECT_EQ(t2->getRecord("cluster"), "sobing over those"); + EXPECT_EQ(t2->getRecord("ronin"), "cheese"); + EXPECT_EQ(t2->getRecord("lama"), "not quite"); + EXPECT_EQ(t2->getRecord("by the shadow"), "face the inevitable"); + EXPECT_EQ(t2->getRecord("summertime"), "curses"); + EXPECT_EQ(t2->getRecord("worry not"), "for shall you"); + + EXPECT_THROW( + s2 = t2->addRecords({ + {"within reasonable limits", "occasion"}, + {"ronin", "crest of violence"}, + {"permanent", "of your kind"} + }), LMDBAL::Exist + ); + EXPECT_EQ(t2->count(), 7); + + LMDBAL::SizeType s3 = c1->addRecords({ + {19, "menace"}, + {-7, "failure driven sorrow"}, + {82, "lungache"}, + {4, "drowsy"}, + {44, "pressure"}, + }); + + EXPECT_EQ(c1->count(), 10); + EXPECT_EQ(s3, 10); + + EXPECT_EQ(c1->getRecord(68), "quality"); + EXPECT_EQ(c1->getRecord(31), "ridgid body"); + EXPECT_EQ(c1->getRecord(16), "fermentation on your kind"); + EXPECT_EQ(c1->getRecord(22), "pseudo"); + EXPECT_EQ(c1->getRecord(-117), "lance of Michael"); + EXPECT_EQ(c1->getRecord(19), "menace"); + EXPECT_EQ(c1->getRecord(-7), "failure driven sorrow"); + EXPECT_EQ(c1->getRecord(82), "lungache"); + EXPECT_EQ(c1->getRecord(4), "drowsy"); + EXPECT_EQ(c1->getRecord(44), "pressure"); + + EXPECT_THROW( + s3 = c1->addRecords({ + {-72, "amber"}, + {-9, "going swinging of paleopathy"}, + {82, "regret"} + }), LMDBAL::Exist + ); + EXPECT_EQ(c1->count(), 10); + + s3 = c1->addRecords({ + {19, "to replicated being"}, + {123, "horibly unforseen"}, + {-32, "stitched"}, + {31, "overall"} + }, true); + EXPECT_EQ(c1->count(), 12); + EXPECT_EQ(s3, 12); + EXPECT_EQ(c1->getRecord(68), "quality"); + EXPECT_EQ(c1->getRecord(31), "overall"); + EXPECT_EQ(c1->getRecord(16), "fermentation on your kind"); + EXPECT_EQ(c1->getRecord(22), "pseudo"); + EXPECT_EQ(c1->getRecord(-117), "lance of Michael"); + EXPECT_EQ(c1->getRecord(19), "to replicated being"); + EXPECT_EQ(c1->getRecord(-7), "failure driven sorrow"); + EXPECT_EQ(c1->getRecord(82), "lungache"); + EXPECT_EQ(c1->getRecord(4), "drowsy"); + EXPECT_EQ(c1->getRecord(44), "pressure"); + EXPECT_EQ(c1->getRecord(-32), "stitched"); + EXPECT_EQ(c1->getRecord(123), "horibly unforseen"); + +}