1
0
forked from blue/lmdbal

got down to the MDB_DUP... flags, some more tests, some docs

This commit is contained in:
Blue 2023-08-18 12:04:37 -03:00
parent 180c40370c
commit f00f017b16
Signed by untrusted user: blue
GPG Key ID: 9B203B252A63EE38
2 changed files with 73 additions and 13 deletions

View File

@ -65,7 +65,9 @@ LMDBAL::Storage<K, V>::~Storage() {
/** /**
* \brief Adds a key-value record to the storage * \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 * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown.
* If your storage doesn't support duplicates LMDBAL::Exist is thrown if the record with the same key already exists in the database.
* If your storage supports duplicates LMDBAL::Exist is thrown only if the record with the same key <b>AND</b> already exists in the database.
* *
* \param[in] key key of the record * \param[in] key key of the record
* \param[in] value value of the record * \param[in] value value of the record
@ -94,7 +96,9 @@ void LMDBAL::Storage<K, V>::addRecord(const K& key, const V& value) {
* This function schedules an addition of a key-value record, but doesn't immidiately adds it. * 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(). * 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 * Take a note that if the storage already had a record you want to add LMDBAL::Exist is thrown.
* If your storage doesn't support duplicates LMDBAL::Exist is thrown if the record with the same key already exists in the database.
* If your storage supports duplicates LMDBAL::Exist is thrown only if the record with the same key <b>AND</b> already exists in the database.
* *
* \param[in] key key of the record * \param[in] key key of the record
* \param[in] value value of the record * \param[in] value value of the record
@ -259,7 +263,7 @@ void LMDBAL::Storage<K, V>::changeRecord(const K& key, const V& value, Transacti
/** /**
* \brief Gets the record from the database * \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 * Take a note that if the storage didn't have a record you want to get LMDBAL::NotFound is thrown
* *
* \param[in] key key of the record you look for * \param[in] key key of the record you look for
* \returns the value from the storage * \returns the value from the storage
@ -279,8 +283,14 @@ V LMDBAL::Storage<K, V>::getRecord(const K& key) const {
/** /**
* \brief Gets the record from the database (reference variant) * \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 * 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.
* 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 * \param[in] key key of the record you look for
* \param[out] value the value from the storage * \param[out] value the value from the storage
@ -310,7 +320,13 @@ void LMDBAL::Storage<K, V>::getRecord(const K& key, V& value) const {
* You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * 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(). * 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 * 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.
* 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 * \param[in] key key of the record you look for
* \param[in] txn transaction ID, can be read only transaction * \param[in] txn transaction ID, can be read only transaction
@ -335,7 +351,13 @@ V LMDBAL::Storage<K, V>::getRecord(const K& key, TransactionID txn) const {
* You can obtain LMDBAL::TransactionID calling LMDBAL::Base::beginReadOnlyTransaction() or LMDBAL::Base::beginTransaction(). * 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(). * 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 * 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.
* 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 * \param[in] key key of the record you look for
* \param[out] value the value from the storage * \param[out] value the value from the storage
@ -818,11 +840,16 @@ inline int LMDBAL::iStorage::makeStorage(MDB_txn* transaction, bool duplicates)
if (duplicates) { if (duplicates) {
flags |= MDB_DUPSORT; flags |= MDB_DUPSORT;
if constexpr (std::is_scalar<K>::value && std::is_scalar<V>::value) if constexpr (std::is_scalar<V>::value)
flags |= MDB_DUPFIXED; flags |= MDB_DUPFIXED;
else if constexpr (std::is_integral<V>::value)
flags |= MDB_INTEGERDUP;
if constexpr (
std::is_same<V, uint32_t>::value ||
std::is_same<V, int32_t>::value ||
std::is_same<V, uint64_t>::value ||
std::is_same<V, int64_t>::value
) //for some reason lmdb breaks if it's not one of these types in MDB_DUPFIXED mode
flags |= MDB_INTEGERDUP;
} }
return mdb_dbi_open(transaction, name.c_str(), flags, &dbi); return mdb_dbi_open(transaction, name.c_str(), flags, &dbi);

View File

@ -1,5 +1,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <limits>
#include "base.h" #include "base.h"
#include "storage.h" #include "storage.h"
@ -10,7 +12,8 @@ protected:
tu1(db->getStorage<int16_t, uint16_t>("sameSizeInts")), tu1(db->getStorage<int16_t, uint16_t>("sameSizeInts")),
tu2(db->getStorage<std::string, int8_t>("stringInt")), tu2(db->getStorage<std::string, int8_t>("stringInt")),
tu3(db->getStorage<float, float>("floatFloat")), tu3(db->getStorage<float, float>("floatFloat")),
tu4(db->getStorage<uint16_t, double>("intDouble")) {} tu4(db->getStorage<uint16_t, double>("intDouble")),
tu5(db->getStorage<float, int64_t>("floatLong")) {}
~DuplicatesTest() {} ~DuplicatesTest() {}
@ -18,6 +21,7 @@ protected:
uint32_t getTU2Flags() const {return tu2->flags();} uint32_t getTU2Flags() const {return tu2->flags();}
uint32_t getTU3Flags() const {return tu3->flags();} uint32_t getTU3Flags() const {return tu3->flags();}
uint32_t getTU4Flags() const {return tu4->flags();} uint32_t getTU4Flags() const {return tu4->flags();}
uint32_t getTU5Flags() const {return tu5->flags();}
static void SetUpTestSuite() { static void SetUpTestSuite() {
if (db == nullptr) { if (db == nullptr) {
@ -26,6 +30,7 @@ protected:
db->addStorage<std::string, int8_t>("stringInt", true); db->addStorage<std::string, int8_t>("stringInt", true);
db->addStorage<float, float>("floatFloat", true); db->addStorage<float, float>("floatFloat", true);
db->addStorage<uint16_t, double>("intDouble", true); db->addStorage<uint16_t, double>("intDouble", true);
db->addStorage<float, int64_t>("floatLong", true);
db->open(); db->open();
} }
@ -44,6 +49,7 @@ protected:
LMDBAL::Storage<std::string, int8_t>* tu2; LMDBAL::Storage<std::string, int8_t>* tu2;
LMDBAL::Storage<float, float>* tu3; LMDBAL::Storage<float, float>* tu3;
LMDBAL::Storage<uint16_t, double>* tu4; LMDBAL::Storage<uint16_t, double>* tu4;
LMDBAL::Storage<float, int64_t>* tu5;
}; };
LMDBAL::Base* DuplicatesTest::db = nullptr; LMDBAL::Base* DuplicatesTest::db = nullptr;
@ -53,6 +59,7 @@ TEST_F(DuplicatesTest, FlagsUnique) {
uint32_t tu2Flags = getTU2Flags(); uint32_t tu2Flags = getTU2Flags();
uint32_t tu3Flags = getTU3Flags(); uint32_t tu3Flags = getTU3Flags();
uint32_t tu4Flags = getTU4Flags(); uint32_t tu4Flags = getTU4Flags();
uint32_t tu5Flags = getTU5Flags();
EXPECT_TRUE(tu1Flags & MDB_INTEGERKEY); EXPECT_TRUE(tu1Flags & MDB_INTEGERKEY);
EXPECT_TRUE(tu1Flags & MDB_DUPSORT); EXPECT_TRUE(tu1Flags & MDB_DUPSORT);
@ -61,8 +68,8 @@ TEST_F(DuplicatesTest, FlagsUnique) {
EXPECT_FALSE(tu2Flags & MDB_INTEGERKEY); EXPECT_FALSE(tu2Flags & MDB_INTEGERKEY);
EXPECT_TRUE(tu2Flags & MDB_DUPSORT); EXPECT_TRUE(tu2Flags & MDB_DUPSORT);
EXPECT_FALSE(tu2Flags & MDB_DUPFIXED); EXPECT_TRUE(tu2Flags & MDB_DUPFIXED);
EXPECT_TRUE(tu2Flags & MDB_INTEGERDUP); EXPECT_FALSE(tu2Flags & MDB_INTEGERDUP);
EXPECT_FALSE(tu3Flags & MDB_INTEGERKEY); EXPECT_FALSE(tu3Flags & MDB_INTEGERKEY);
EXPECT_TRUE(tu3Flags & MDB_DUPSORT); EXPECT_TRUE(tu3Flags & MDB_DUPSORT);
@ -73,6 +80,11 @@ TEST_F(DuplicatesTest, FlagsUnique) {
EXPECT_TRUE(tu4Flags & MDB_DUPSORT); EXPECT_TRUE(tu4Flags & MDB_DUPSORT);
EXPECT_TRUE(tu4Flags & MDB_DUPFIXED); EXPECT_TRUE(tu4Flags & MDB_DUPFIXED);
EXPECT_FALSE(tu4Flags & MDB_INTEGERDUP); EXPECT_FALSE(tu4Flags & MDB_INTEGERDUP);
EXPECT_FALSE(tu5Flags & MDB_INTEGERKEY);
EXPECT_TRUE(tu5Flags & MDB_DUPSORT);
EXPECT_TRUE(tu5Flags & MDB_DUPFIXED);
EXPECT_TRUE(tu5Flags & MDB_INTEGERDUP);
} }
TEST_F(DuplicatesTest, AddingPairUnique) { TEST_F(DuplicatesTest, AddingPairUnique) {
@ -131,4 +143,25 @@ TEST_F(DuplicatesTest, AddingPairUnique) {
EXPECT_EQ(tu4->count(), 4); EXPECT_EQ(tu4->count(), 4);
EXPECT_EQ(tu4->getRecord(172), 0.00000001); EXPECT_EQ(tu4->getRecord(172), 0.00000001);
EXPECT_EQ(tu4->getRecord(327), 463.28348); //since they are not int's they are compared sort of lexicographically EXPECT_EQ(tu4->getRecord(327), 463.28348); //since they are not int's they are compared sort of lexicographically
tu5->addRecord(-84.7, 45656753);
EXPECT_THROW(tu5->addRecord(-84.7, 45656753), LMDBAL::Exist);
tu5->addRecord(-84.7, 45656754);
int64_t intMax = std::numeric_limits<int32_t>::max();
int64_t intMin = std::numeric_limits<int32_t>::min();
int64_t longMax = std::numeric_limits<int64_t>::max();
int64_t longMin = std::numeric_limits<int64_t>::min();
tu5->addRecord(52.87, intMax);
EXPECT_THROW(tu5->addRecord(52.87, intMax), LMDBAL::Exist);
tu5->addRecord(52.87, intMin);
EXPECT_THROW(tu5->addRecord(52.87, intMin), LMDBAL::Exist);
tu5->addRecord(52.87, longMax);
EXPECT_THROW(tu5->addRecord(52.87, longMax), LMDBAL::Exist);
tu5->addRecord(52.87, longMin);
EXPECT_THROW(tu5->addRecord(52.87, longMin), LMDBAL::Exist);
EXPECT_EQ(tu5->count(), 6);
EXPECT_EQ(tu5->getRecord(-84.7), 45656753);
EXPECT_EQ(tu5->getRecord(52.87), intMax);
} }