duplicates handling for forceRecord and changeRecord methods, tests and docs

This commit is contained in:
Blue 2023-08-19 16:25:52 -03:00
parent f00f017b16
commit 2d40692560
Signed by: blue
GPG Key ID: 9B203B252A63EE38
4 changed files with 263 additions and 43 deletions

View File

@ -47,6 +47,7 @@ template <class K, class V>
class Cache;
typedef MDB_txn* TransactionID; /**<I'm going to use transaction pointers as transaction IDs*/
typedef uint32_t SizeType; /**<All LMDBAL sizes are uint32_t*/
class Base {
friend class iStorage;

View File

@ -20,6 +20,7 @@
#define LMDBAL_STORAGE_H
#include <type_traits>
#include <cstring>
#include "base.h"
#include "serializer.h"
@ -30,8 +31,6 @@ class DuplicatesTest;
namespace LMDBAL {
typedef uint32_t SizeType;
class iStorage {
friend class Base;
public:

View File

@ -93,7 +93,7 @@ void LMDBAL::Storage<K, V>::addRecord(const K& key, const V& value) {
/**
* \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.
* This method 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.
@ -128,8 +128,15 @@ void LMDBAL::Storage<K, V>::addRecord(const K& key, const V& value, TransactionI
/**
* \brief Adds a key-value record to the storage, overwrites if it already exists
*
* This method is mostly useless in duplicates mode.
* In this mode it basically does the same thing LMDBAL::Storage::addRecord() does,
* but suppresses LMDBAL::Exist exception if the record with the same key-value pair existed in the storage.
* In this case just false is returned from the method.
*
* \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
@ -155,10 +162,15 @@ bool LMDBAL::Storage<K, V>::forceRecord(const K& key, const V& value) {
/**
* \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.
* This method schedules an addition of a key-value record, but doesn't immidiately add 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().
*
* This method is mostly useless in duplicates mode.
* In this mode it basically does the same thing LMDBAL::Storage::addRecord() does,
* but suppresses LMDBAL::Exist exception if the record with the same key-value pair existed in the storage.
* In this case just false is returned from the method.
*
* \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!
@ -172,27 +184,35 @@ bool LMDBAL::Storage<K, V>::forceRecord(const K& key, const V& value, Transactio
ensureOpened(forceRecordMethodName);
bool added;
MDB_val lmdbKey = keySerializer.setData(key);
MDB_val lmdbData;
int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData);
switch (rc) {
case MDB_SUCCESS:
added = false;
break;
case MDB_NOTFOUND:
if (duplicates) {
try {
addRecord(key, value, txn);
added = true;
break;
default:
} catch (const LMDBAL::Exist& e) {
added = false;
}
} else {
MDB_val lmdbKey = keySerializer.setData(key);
MDB_val lmdbData;
int rc = mdb_get(txn, dbi, &lmdbKey, &lmdbData);
switch (rc) {
case MDB_SUCCESS:
added = false;
break;
case MDB_NOTFOUND:
added = true;
break;
default:
added = false;
throwUnknown(rc);
}
lmdbData = valueSerializer.setData(value);
rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0);
if (rc != MDB_SUCCESS)
throwUnknown(rc);
}
lmdbData = valueSerializer.setData(value);
rc = mdb_put(txn, dbi, &lmdbKey, &lmdbData, 0);
if (rc != MDB_SUCCESS)
throwUnknown(rc);
return added;
}
@ -201,10 +221,17 @@ bool LMDBAL::Storage<K, V>::forceRecord(const K& key, const V& value, Transactio
*
* Take a note that if the storage didn't have a record you want to change LMDBAL::NotFound is thrown
*
* If duplicates mode is enabled this function will find the first entry of the key
* (which is pretty couterintuitive, see LMDBAL::Storage::getRecord() description)
* and change it's value to the given one.
* If the given value matches some of the other values for given key the method will throw LMDBAL::Exist,
* if no key was found it will still throw LMDBAL::NotFound.
*
* \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::Exist thrown in duplicates mode when the given value matches some of existing values for the given key
* \exception LMDBAL::Closed thrown if the database was not opened
* \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb
*/
@ -226,16 +253,23 @@ void LMDBAL::Storage<K, V>::changeRecord(const K& key, const V& value) {
/**
* \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.
* This method 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
*
* If duplicates mode is enabled this function will find the first entry of the key
* (which is pretty couterintuitive, see LMDBAL::Storage::getRecord() description)
* and change it's value to the given one.
* If the given value matches some of the other values for given key the method will throw LMDBAL::Exist,
* if no key was found it will still throw LMDBAL::NotFound.
*
* \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::Exist thrown in duplicates mode when the given value matches some of existing values for the given key
* \exception LMDBAL::Closed thrown if the database was not opened
* \exception LMDBAL::Unknown thrown if something unexpected happend within lmdb
*/
@ -249,15 +283,34 @@ void LMDBAL::Storage<K, V>::changeRecord(const K& key, const V& value, Transacti
throwUnknown(rc);
MDB_val lmdbKey = keySerializer.setData(key);
rc = mdb_cursor_get(cursor, &lmdbKey, nullptr, MDB_SET);
MDB_val lmdbData;
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, 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_val lmdbNewData = valueSerializer.setData(value);
bool sameSize = lmdbData.mv_size == lmdbNewData.mv_size;
int firstDifferentByte = 0;
if (sameSize) { //can compare only if they are the same size
firstDifferentByte = memcmp(lmdbData.mv_data, lmdbNewData.mv_data, lmdbData.mv_size);
if (firstDifferentByte == 0) { //old and new is the same, nothing to do
mdb_cursor_close(cursor);
return;
}
}
unsigned int flags = MDB_CURRENT;
if (duplicates && (!sameSize || firstDifferentByte < 0)) { //if new value is greater than the old one
rc = mdb_cursor_del(cursor, 0); //we need to initiate duplicates sort, for it to be in the correct place
flags = MDB_NODUPDATA;
}
if (rc == MDB_SUCCESS)
rc = mdb_cursor_put(cursor, &lmdbKey, &lmdbNewData, flags);
mdb_cursor_close(cursor);
if (rc != MDB_SUCCESS)
throwUnknown(rc);
throwDuplicateOrUnknown(rc, toString(key));
}
/**
@ -265,6 +318,12 @@ void LMDBAL::Storage<K, V>::changeRecord(const K& key, const V& value, Transacti
* Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown
*
* If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb.
* It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result.
* Anyway:
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as </b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by </b>UNSIGNED</b> comparison.
* - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50"
*
* \param[in] key key of the record you look for
* \returns the value from the storage
*
@ -287,7 +346,7 @@ V LMDBAL::Storage<K, V>::getRecord(const K& key) const {
* Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown
*
* If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb.
* It's not very straight forward, so, you shouldn't really use this function if you use duplicates and you rely on exact result.
* It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result.
* Anyway:
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as </b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by </b>UNSIGNED</b> comparison.
* - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50"
@ -323,7 +382,7 @@ void LMDBAL::Storage<K, V>::getRecord(const K& key, V& value) const {
* Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown
*
* If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb.
* It's not very straight forward, so, you shouldn't really use this function if you use duplicates and you rely on exact result.
* It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result.
* Anyway:
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as </b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by </b>UNSIGNED</b> comparison.
* - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50"
@ -354,7 +413,7 @@ V LMDBAL::Storage<K, V>::getRecord(const K& key, TransactionID txn) const {
* Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown
*
* If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb.
* It's not very straight forward, so, you shouldn't really use this function if you use duplicates and you rely on exact result.
* It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result.
* Anyway:
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as </b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by </b>UNSIGNED</b> comparison.
* - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50"
@ -540,7 +599,7 @@ void LMDBAL::Storage<K, V>::readAll(std::map<K, V>& result, TransactionID txn) c
/**
* \brief Replaces the content of the whole storage with the given
*
* Basically this function drops the database and adds all the records from the given map
* Basically this method drops the database and adds all the records from the given map
*
* \param[in] data new data of the storage
*
@ -565,8 +624,8 @@ void LMDBAL::Storage<K, V>::replaceAll(const std::map<K, V>& data) {
/**
* \brief Replaces the content of the whole storage with the given (transaction variant)
*
* Basically this function drops the database and adds all the records from the given map
* This function schedules a data replacement, but doesn't immidiately execute it.
* Basically this method drops the database and adds all the records from the given map
* This method schedules a data replacement, but doesn't immidiately execute it.
* You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction().
*
* \param[in] data new data of the storage
@ -598,7 +657,7 @@ void LMDBAL::Storage<K, V>::replaceAll(const std::map<K, V>& data, TransactionID
* \brief Adds records in bulk
*
* \param[in] data the data to be added
* \param[in] overwrite if false function throws LMDBAL::Exist on repeated key, if true - overwrites it
* \param[in] overwrite if false method throws LMDBAL::Exist on repeated key, if true - overwrites it
* \returns new actual amount of records in the storage
*
* \exception LMDBAL::Closed thrown if the database was not opened
@ -625,12 +684,12 @@ uint32_t LMDBAL::Storage<K, V>::addRecords(const std::map<K, V>& data, bool over
/**
* \brief Adds records in bulk (transaction variant)
*
* This function schedules a data addition, but doesn't immidiately execute it.
* This method schedules a data addition, but doesn't immidiately execute it.
* You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction().
*
* \param[in] data the data to be added
* \param[in] txn transaction ID, needs to be a writable transaction!
* \param[in] overwrite if false function throws LMDBAL::Exist on repeated key, if true - overwrites it
* \param[in] overwrite if false method throws LMDBAL::Exist on repeated key, if true - overwrites it
* \returns new actual amount of records in the storage
*
* \exception LMDBAL::Closed thrown if the database was not opened
@ -693,7 +752,7 @@ void LMDBAL::Storage<K, V>::removeRecord(const K& key) {
* \brief Removes one of the records (transaction variant)
*
* Take a note that if the storage didn't have a record you want to remove LMDBAL::NotFound is thrown
* This function schedules a record removal, but doesn't immidiately execute it.
* This method schedules a record removal, but doesn't immidiately execute it.
* You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginTransaction().
*
* \param[in] key key of the record you wish to be removed
@ -714,7 +773,7 @@ void LMDBAL::Storage<K, V>::removeRecord(const K& key, TransactionID txn) {
}
/**
* \brief A private virtual function I need to open each storage in the database
* \brief A private virtual method I need to open each storage in the database
*
* \param[in] transaction - lmdb transaction to call <a class="el" href="http://www.lmdb.tech/doc/group__mdb.html#gac08cad5b096925642ca359a6d6f0562a">mdb_dbi_open</a>
* \returns MDB_SUCCESS if everything went smooth or MDB_<error> -like error code
@ -725,7 +784,7 @@ int LMDBAL::Storage<K, V>::open(MDB_txn* transaction) {
}
/**
* \brief A private virtual function I need to close each storage in the database
* \brief A private virtual method I need to close each storage in the database
*/
template<class K, class V>
void LMDBAL::Storage<K, V>::close() {
@ -751,7 +810,7 @@ LMDBAL::Cursor<K, V>* LMDBAL::Storage<K, V>::createCursor() {
/**
* \brief Reads current storage flags it was opened with
*
* This function exists mostly for testing purposes
* This method exists mostly for testing purposes
*
* \returns Third out parameter of <a href="http://www.lmdb.tech/doc/group__internal.html#ga95ba4cb721035478a8705e57b91ae4d4">mdb_dbi_flags</a> function
*
@ -792,7 +851,7 @@ void LMDBAL::Storage<K, V>::destroyCursor(Cursor<K, V>* cursor) {
}
/**
* \brief A private virtual function that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache
* \brief A private virtual method that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache
*
* \param[in] key a key of discovered record
* \param[in] value a value of discovered record
@ -804,7 +863,7 @@ void LMDBAL::Storage<K, V>::discoveredRecord(const K& key, const V& value) const
}
/**
* \brief A private virtual function that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache
* \brief A private virtual method that cursor calls when he reads a record, does nothing here but populates the LMDBAL::Cache
*
* \param[in] key a key of discovered record
* \param[in] value a value of discovered record

View File

@ -54,7 +54,7 @@ protected:
LMDBAL::Base* DuplicatesTest::db = nullptr;
TEST_F(DuplicatesTest, FlagsUnique) {
TEST_F(DuplicatesTest, Flags) {
uint32_t tu1Flags = getTU1Flags();
uint32_t tu2Flags = getTU2Flags();
uint32_t tu3Flags = getTU3Flags();
@ -87,7 +87,7 @@ TEST_F(DuplicatesTest, FlagsUnique) {
EXPECT_TRUE(tu5Flags & MDB_INTEGERDUP);
}
TEST_F(DuplicatesTest, AddingPairUnique) {
TEST_F(DuplicatesTest, Adding) {
tu1->addRecord(1, 1);
tu1->addRecord(2, 2);
tu1->addRecord(2, 1);
@ -165,3 +165,164 @@ TEST_F(DuplicatesTest, AddingPairUnique) {
EXPECT_EQ(tu5->getRecord(-84.7), 45656753);
EXPECT_EQ(tu5->getRecord(52.87), intMax);
}
TEST_F(DuplicatesTest, Forcing) {
LMDBAL::SizeType tu1Size = tu1->count();
tu1->addRecord(-56, 71);
tu1->addRecord(-56, 274);
tu1->addRecord(-56, 732);
EXPECT_EQ(tu1->count(), tu1Size += 3);
EXPECT_TRUE(tu1->forceRecord(-56, 322));
EXPECT_EQ(tu1->count(), tu1Size += 1);
EXPECT_EQ(tu1->getRecord(-56), 274); //like yeah, it's really counterintuitive, since it's compared byte by byte
EXPECT_TRUE(tu1->forceRecord(-56, 14));
EXPECT_EQ(tu1->count(), tu1Size += 1);
EXPECT_EQ(tu1->getRecord(-56), 14);
EXPECT_FALSE(tu1->forceRecord(-56, 274));
EXPECT_EQ(tu1->count(), tu1Size);
LMDBAL::SizeType tu2Size = tu2->count();
tu2->addRecord("printable", -2);
tu2->addRecord("printable", 4);
EXPECT_EQ(tu2->count(), tu2Size += 2);
EXPECT_TRUE(tu2->forceRecord("printable", 18));
EXPECT_EQ(tu2->count(), tu2Size += 1);
EXPECT_EQ(tu2->getRecord("printable"), 4);
EXPECT_TRUE(tu2->forceRecord("printable", 3));
EXPECT_EQ(tu2->count(), tu2Size += 1);
EXPECT_EQ(tu2->getRecord("printable"), 3);
EXPECT_FALSE(tu2->forceRecord("printable", 4));
EXPECT_EQ(tu2->count(), tu2Size);
LMDBAL::SizeType tu3Size = tu3->count();
tu3->addRecord(17.3, 93.21);
tu3->addRecord(17.3, 6.6);
tu3->addRecord(17.3, 105.1);
EXPECT_EQ(tu3->count(), tu3Size += 3);
EXPECT_TRUE(tu3->forceRecord(17.3, 74.9));
EXPECT_EQ(tu3->count(), tu3Size += 1);
EXPECT_EQ(tu3->getRecord(17.3), 105.1f); //here too, really one should not use this function with duplicates,
EXPECT_TRUE(tu3->forceRecord(17.3, 5.1)); //unless he wishes for kinda randomish result
EXPECT_EQ(tu3->count(), tu3Size += 1);
EXPECT_EQ(tu3->getRecord(17.3), 5.1f);
EXPECT_FALSE(tu3->forceRecord(17.3, 93.21));
EXPECT_EQ(tu3->count(), tu3Size);
LMDBAL::SizeType tu4Size = tu4->count();
tu4->addRecord(84, -359.109);
tu4->addRecord(84, 2879.654);
EXPECT_EQ(tu4->count(), tu4Size += 2);
EXPECT_TRUE(tu4->forceRecord(84, 72.9));
EXPECT_EQ(tu4->count(), tu4Size += 1);
EXPECT_EQ(tu4->getRecord(84), 2879.654);
EXPECT_TRUE(tu4->forceRecord(84, 2679.5));
EXPECT_EQ(tu4->count(), tu4Size += 1);
EXPECT_EQ(tu4->getRecord(84), 2679.5);
EXPECT_FALSE(tu4->forceRecord(84, -359.109));
EXPECT_EQ(tu4->count(), tu4Size);
LMDBAL::SizeType tu5Size = tu5->count();
tu5->addRecord(0.45, -85645);
tu5->addRecord(0.45, 10573);
tu5->addRecord(0.45, 573);
tu5->addRecord(0.45, 73285);
EXPECT_EQ(tu5->count(), tu5Size += 4);
EXPECT_TRUE(tu5->forceRecord(0.45, -473));
EXPECT_EQ(tu5->count(), tu5Size += 1);
EXPECT_EQ(tu5->getRecord(0.45), 573);
EXPECT_TRUE(tu5->forceRecord(0.45, 394));
EXPECT_EQ(tu5->count(), tu5Size += 1);
EXPECT_EQ(tu5->getRecord(0.45), 394);
EXPECT_FALSE(tu5->forceRecord(0.45, 10573));
EXPECT_EQ(tu5->count(), tu5Size);
}
TEST_F(DuplicatesTest, Changing) {
LMDBAL::SizeType tu1Size = tu1->count();
EXPECT_THROW(tu1->changeRecord(-31, 53), LMDBAL::NotFound);
EXPECT_EQ(tu1->count(), tu1Size);
tu1->addRecord(-31, 53);
EXPECT_EQ(tu1->count(), tu1Size += 1);
EXPECT_EQ(tu1->getRecord(-31), 53);
tu1->changeRecord(-31, 53); //should just do nothing usefull, but work normally
EXPECT_EQ(tu1->getRecord(-31), 53);
tu1->changeRecord(-31, 19);
EXPECT_EQ(tu1->count(), tu1Size);
EXPECT_EQ(tu1->getRecord(-31), 19);
tu1->addRecord(-31, 60);
EXPECT_EQ(tu1->count(), tu1Size += 1);
EXPECT_EQ(tu1->getRecord(-31), 19);
tu1->changeRecord(-31, 16);
EXPECT_EQ(tu1->count(), tu1Size);
EXPECT_EQ(tu1->getRecord(-31), 16);
tu1->changeRecord(-31, 203);
EXPECT_EQ(tu1->count(), tu1Size);
EXPECT_EQ(tu1->getRecord(-31), 60);
EXPECT_THROW(tu1->changeRecord(-31, 203), LMDBAL::Exist);
LMDBAL::SizeType tu2Size = tu2->count();
EXPECT_THROW(tu2->changeRecord("jeremy spins", -5), LMDBAL::NotFound);
EXPECT_EQ(tu2->count(), tu2Size);
tu2->addRecord("jeremy spins", -5);
EXPECT_EQ(tu2->count(), tu2Size += 1);
EXPECT_EQ(tu2->getRecord("jeremy spins"), -5);
tu2->changeRecord("jeremy spins", -5); //should just do nothing usefull, but work normally
EXPECT_EQ(tu2->getRecord("jeremy spins"), -5);
tu2->changeRecord("jeremy spins", 11);
EXPECT_EQ(tu2->count(), tu2Size);
EXPECT_EQ(tu2->getRecord("jeremy spins"), 11);
tu2->addRecord("jeremy spins", 24);
EXPECT_EQ(tu2->count(), tu2Size += 1);
EXPECT_EQ(tu2->getRecord("jeremy spins"), 11);
tu2->changeRecord("jeremy spins", 4);
EXPECT_EQ(tu2->count(), tu2Size);
EXPECT_EQ(tu2->getRecord("jeremy spins"), 4);
tu2->changeRecord("jeremy spins", -7);
EXPECT_EQ(tu2->count(), tu2Size);
EXPECT_EQ(tu2->getRecord("jeremy spins"), 24); //cuz it's compared as usigned down there
EXPECT_THROW(tu2->changeRecord("jeremy spins", -7), LMDBAL::Exist);
LMDBAL::SizeType tu3Size = tu3->count();
EXPECT_THROW(tu3->changeRecord(26.7, 68.22), LMDBAL::NotFound);
EXPECT_EQ(tu3->count(), tu3Size);
tu3->addRecord(26.7, 68.22);
EXPECT_EQ(tu3->count(), tu3Size += 1);
EXPECT_EQ(tu3->getRecord(26.7), 68.22f);
tu3->changeRecord(26.7, 68.22); //should just do nothing usefull, but work normally
EXPECT_EQ(tu3->getRecord(26.7), 68.22f);
tu3->changeRecord(26.7, 23.18);
EXPECT_EQ(tu3->count(), tu3Size);
EXPECT_EQ(tu3->getRecord(26.7), 23.18f);
tu3->addRecord(26.7, 22.16);
EXPECT_EQ(tu3->count(), tu3Size += 1);
EXPECT_EQ(tu3->getRecord(26.7), 23.18f);
tu3->changeRecord(26.7, 21.7);
EXPECT_EQ(tu3->count(), tu3Size);
EXPECT_EQ(tu3->getRecord(26.7), 21.7f);
tu3->changeRecord(26.7, 54.33);
EXPECT_EQ(tu3->count(), tu3Size);
EXPECT_EQ(tu3->getRecord(26.7), 22.16f);
EXPECT_THROW(tu3->changeRecord(26.7, 54.33), LMDBAL::Exist);
LMDBAL::SizeType tu4Size = tu4->count();
EXPECT_THROW(tu4->changeRecord(852, 6795.349), LMDBAL::NotFound);
EXPECT_EQ(tu4->count(), tu4Size);
tu4->addRecord(852, 6795.349);
EXPECT_EQ(tu4->count(), tu4Size += 1);
EXPECT_EQ(tu4->getRecord(852), 6795.349);
tu4->changeRecord(852, 6795.349); //should just do nothing usefull, but work normally
EXPECT_EQ(tu4->getRecord(852), 6795.349);
tu4->changeRecord(852, 13.54);
EXPECT_EQ(tu4->count(), tu4Size);
EXPECT_EQ(tu4->getRecord(852), 13.54);
tu4->addRecord(852, 213.85);
EXPECT_EQ(tu4->count(), tu4Size += 1);
EXPECT_EQ(tu4->getRecord(852), 13.54);
tu4->changeRecord(852, 236.21);
EXPECT_EQ(tu4->count(), tu4Size);
EXPECT_EQ(tu4->getRecord(852), 236.21);
tu4->changeRecord(852, 46324.1135);
EXPECT_EQ(tu4->count(), tu4Size);
EXPECT_EQ(tu4->getRecord(852), 213.85);
EXPECT_THROW(tu4->changeRecord(852, 46324.1135), LMDBAL::Exist);
}