diff --git a/doc/mainpage.dox b/doc/mainpage.dox
index 2192aa4..2f51d77 100644
--- a/doc/mainpage.dox
+++ b/doc/mainpage.dox
@@ -1,12 +1,12 @@
/*! \mainpage Getting Started
*
- * Everything begins with a data nase represented by the class LMDBAL::Base.
+ * Everything begins with a data base represented by the class LMDBAL::Base.
* It repesents a collection of key-value storages that are going to be stored in a sigle data base directory.
* To create a LMDBAL::Base you need to pick up a name of a directory that is going to be created on your machine.
*
* LMDBAL::Base creates or opens existing directory with the given name in the location acquired with
* QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
- * so, the file system destination of your data would depend on the
+ * so, the file system destination of your data depends on the
* QCoreApplication configuration of your app.
*
* After you have created a LMDBAL::Base you probably want to obtain storage handlers.
@@ -15,10 +15,10 @@
* std::map
* to speed up the access.
*
- * You can obtain handlers by calling LMDBAL::Base::addStorage(const std::string&, bool) or LMDBAL::Base::addCache(const std::string& name).
+ * You can obtain handlers by calling LMDBAL::Base::addStorage() or LMDBAL::Base::addCache().
* Note that the handlers still belong to the LMDBAL::Base and it's his responsibility to destroy them.
* You are not obliged to save those handlers,
- * you can obtain them at any time later using methods LMDBAL::Base::getStorage(const std::string&) or LMDBAL::Base::getCache(const std::string&)
+ * you can obtain them at any time later using methods LMDBAL::Base::getStorage() or LMDBAL::Base::getCache()
* calling them with the same template types and names.
*
* After you have added all the storages you wanted it's time to open the data base with LMDBAL::Base::open().
diff --git a/src/base.cpp b/src/base.cpp
index 604f9da..81f9bdb 100644
--- a/src/base.cpp
+++ b/src/base.cpp
@@ -216,6 +216,14 @@ void LMDBAL::Base::drop() {
/**
* \brief Begins read-only transaction
*
+ * This is the legitimate way to retrieve LMDBAL::Transaction.
+ * LMDBAL::Transaction is considered runnig right after creation by this method.
+ * You can terminate transaction manually calling LMDBAL::Transaction::terminate
+ * but it's not required, because transaction will be terminated automatically
+ * (if it was not terminated manually) upon the call of the destructor.
+ *
+ * You can not use termitated transaction any more.
+ *
* \returns read-only transaction
*
* \exception LMDBAL::Closed - thrown if the database is closed
@@ -229,6 +237,16 @@ LMDBAL::Transaction LMDBAL::Base::beginReadOnlyTransaction() const {
/**
* \brief Begins writable transaction
*
+ * This is the legitimate way to retrieve LMDBAL::WriteTransaction.
+ * LMDBAL::WriteTransaction is considered runnig right after creation by this method.
+ * You can commit all the changes made by this transaction calling LMDBAL::WriteTransaction::commit.
+ * You can cancel any changes made bu this transaction calling LMDBAL::WriteTransaction::abort
+ * (or LMDBAL::Transaction::terminate which LMDBAL::WriteTransaction inherits),
+ * but it's not required, because transaction will be aborted automatically
+ * (if it was not terminated (committed OR aborted) manually) upon the call of the destructor.
+ *
+ * You can not use termitated transaction any more.
+ *
* \returns writable transaction
*
* \exception LMDBAL::Closed - thrown if the database is closed
diff --git a/src/cursor.hpp b/src/cursor.hpp
index 00fc346..cbc293f 100644
--- a/src/cursor.hpp
+++ b/src/cursor.hpp
@@ -186,7 +186,7 @@ void LMDBAL::Cursor::renew () const {
*
* This function does nothing if the cursor is closed
*
- * \param[in] txn a transaction you wish this cursor to be bound to
+ * \param[in] transaction - a transaction you wish this cursor to be bound to
*
* \exception LMDBAL::Closed thrown if you try to renew the cursor on a closed database
* \exception LMDBAL::Unknown thrown if there was a problem renewing the cursor by lmdb
diff --git a/src/storage.cpp b/src/storage.cpp
index 26614dd..f1258fa 100644
--- a/src/storage.cpp
+++ b/src/storage.cpp
@@ -439,4 +439,10 @@ void LMDBAL::iStorage::transactionCommited(LMDBAL::TransactionID txn) {
void LMDBAL::iStorage::transactionAborted(LMDBAL::TransactionID txn) const {
UNUSED(txn);}
+/**
+ * \brief A method where database additionally handles drop
+ *
+ * It's a protected method that is called to optimise drop process
+ * after the transaction is commited. Used just for optimisations.
+ */
void LMDBAL::iStorage::handleDrop() {}
diff --git a/src/transaction.cpp b/src/transaction.cpp
index 47744b4..3632ad6 100644
--- a/src/transaction.cpp
+++ b/src/transaction.cpp
@@ -1,17 +1,42 @@
#include "transaction.h"
+/**
+ * \class LMDBAL::Transaction
+ * \brief Public read only transaction
+ *
+ * This class provides read only transactions you can use
+ * to speed to your queries keeping them thread safe.
+ * LMDBAL::Transaction is NOT COPYABLE but MOVABLE.
+ * Transaction can be in one of two states: active or terminated.
+ * The way to receive an active LMDBAL::Transaction is to call LMDBAL::Base::beginReadOnlyTransaction.
+ *
+ * Active transactions become terminated upon the call of LMDBAL::Transaction::terminate.
+ * Active transactions automaticaly terminate themselves upon destruction.
+ *
+ * You CAN NOT use inactive transactions for any query.
+ */
+
+/**
+ * \brief Constructs inactive transaction
+ */
LMDBAL::Transaction::Transaction():
txn(nullptr),
active(false),
parent(nullptr)
{}
+/**
+ * \brief Constructs an active transaction
+ */
LMDBAL::Transaction::Transaction(TransactionID txn, const Base* parent) :
txn(txn),
active(true),
parent(parent)
{}
+/**
+ * \brief Moves transaction to a new object
+ */
LMDBAL::Transaction::Transaction(Transaction&& other):
txn(other.txn),
active(other.active),
@@ -20,10 +45,16 @@ LMDBAL::Transaction::Transaction(Transaction&& other):
other.active = false;
}
+/**
+ * \brief Destroys transaction
+ */
LMDBAL::Transaction::~Transaction() {
terminate();
}
+/**
+ * \brief Move-assigns transaction to the new object
+ */
LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) {
terminate();
@@ -36,6 +67,11 @@ LMDBAL::Transaction& LMDBAL::Transaction::operator=(Transaction&& other) {
return *this;
}
+/**
+ * \brief Terminates transaction if it was active
+ *
+ * Transaction becomes terminated after calling this method
+ */
void LMDBAL::Transaction::terminate() {
if (active) {
parent->abortTransaction(txn);
@@ -43,26 +79,73 @@ void LMDBAL::Transaction::terminate() {
}
}
+/**
+ * \brief Returns transaction states
+ *
+ * \returns true if the transaction is active, false otherwise
+ */
bool LMDBAL::Transaction::isActive() const {
return active; //todo may be it's better if I query it from DB?
}
+
+/**
+ * \class LMDBAL::WriteTransaction
+ * \brief Public writable transaction
+ *
+ * This class provides writable transactions you can use
+ * to speed to your queries and modifications keeping them thread safe.
+ * LMDBAL::WriteTransaction is NOT COPYABLE but MOVABLE.
+ * Transaction can be in one of two states: active or terminated.
+ * The way to receive an active LMDBAL::WriteTransaction is to call LMDBAL::Base::beginTransaction.
+ * You can use LMDBAL::WriteTransaction for everything instead of LMDBAL::Transaction
+ *
+ * Active transactions become terminated upon the call of
+ * LMDBAL::WriteTransaction::abort or LMDBAL::WriteTransaction::commit.
+ * Calling LMDBAL::Transaction::terminate on LMDBAL::WriteTransaction
+ * is exactly the same as calling LMDBAL::WriteTransaction::abort.
+ *
+ * Active transactions automaticaly terminate themselves upon destruction.
+ * For LMDBAL::WriteTransaction default behaviour upon destruction is to abort.
+ *
+ * You CAN NOT use inactive transactions for any query.
+ */
+
+/**
+ * \brief Constructs active write transaction
+ */
LMDBAL::WriteTransaction::WriteTransaction(TransactionID txn, Base* parent):
Transaction(txn, parent)
{}
+/**
+ * \brief Constructs inactive write transaction
+ */
LMDBAL::WriteTransaction::WriteTransaction():
Transaction()
{}
+/**
+ * \brief Moves transaction to the new object
+ */
LMDBAL::WriteTransaction::WriteTransaction(WriteTransaction&& other):
Transaction(std::move(other))
{}
+/**
+ * \brief Aborts transaction cancelling all changes
+ *
+ * Transaction becomes terminated after calling this method
+ */
void LMDBAL::WriteTransaction::abort() {
terminate();
}
+/**
+ * \brief Commits transaction submitting all changes
+ *
+ * Transaction becomes terminated after calling this method
+ */
void LMDBAL::WriteTransaction::commit() {
if (active) {
const_cast(parent)->commitTransaction(txn);
diff --git a/src/transaction.h b/src/transaction.h
index 77ce26a..43bb69e 100644
--- a/src/transaction.h
+++ b/src/transaction.h
@@ -23,9 +23,9 @@ protected:
Transaction(TransactionID txn, const Base* parent);
protected:
- TransactionID txn;
- bool active;
- const Base* parent;
+ TransactionID txn; /**<\brief Transaction inner handler*/
+ bool active; /**<\brief Transaction state*/
+ const Base* parent; /**<\brief Pointer to the database this transaction belongs to*/
};
class WriteTransaction : public Transaction {
diff --git a/test/cachetransaction.cpp b/test/cachetransaction.cpp
index b86fd2f..6092dfb 100644
--- a/test/cachetransaction.cpp
+++ b/test/cachetransaction.cpp
@@ -229,3 +229,51 @@ TEST_F(CacheTransactionsTest, ConcurentModification) {
EXPECT_EQ(c1->getRecord(5), -46);
}
+TEST_F(CacheTransactionsTest, RAIIResourceFree) {
+ EXPECT_EQ(db->ready(), true);
+
+ int pid = fork();
+ if (pid == 0) { // I am the child
+ std::cout << "beggining child transaction" << std::endl;
+ LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
+ //and wait for the first transaction to get finished
+ std::cout << "checking result of the parent transaction value" << std::endl;
+ EXPECT_FALSE(c1->checkRecord(221, txn2));
+
+ std::cout << "performing modification from the child thread" << std::endl;
+ c1->addRecord(221, 14, txn2);
+
+ std::cout << "commiting child transaction" << std::endl;
+ txn2.commit();
+
+ std::cout << "quitting child thread, letting child transaction be destroyed after commit" << std::endl;
+ exit(testing::Test::HasFailure());
+ } else { // I am the parent
+ std::cout << "beggining parent transaction" << std::endl;
+ {
+ LMDBAL::WriteTransaction txn1 = db->beginTransaction();
+
+ std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
+ usleep(5);
+ std::cout << "parent thread woke up" << std::endl;
+
+ std::cout << "adding value from parent thread" << std::endl;
+ c1->addRecord(221, 320, txn1);
+
+ std::cout << "checking value from parent thread using transaction" << std::endl;
+ EXPECT_EQ(c1->getRecord(221, txn1), 320);
+
+ std::cout << "checking value independently from parent thread" << std::endl;
+ EXPECT_FALSE(c1->checkRecord(221));
+
+ std::cout << "implicitly aborting transaction by leaving the scope" << std::endl;
+ }
+
+ std::cout << "child thread should resume after this line" << std::endl;
+ ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems
+ }
+
+ std::cout << "checking the final result" << std::endl;
+ EXPECT_EQ(c1->getRecord(221), 14);
+}
+
diff --git a/test/storagetransaction.cpp b/test/storagetransaction.cpp
index 1d93946..ad21846 100644
--- a/test/storagetransaction.cpp
+++ b/test/storagetransaction.cpp
@@ -14,7 +14,7 @@ protected:
~StorageTransactionsTest() {}
- int waitForChildFork(int pid) {
+ int waitForChildFork(int pid) {
int status;
if (0 > waitpid(pid, &status, 0)) {
std::cerr << "[----------] Waitpid error!" << std::endl;
@@ -22,9 +22,9 @@ protected:
}
if (WIFEXITED(status)) {
const int exit_status = WEXITSTATUS(status);
- if (exit_status != 0) {
+ if (exit_status != 0)
std::cerr << "[----------] Non-zero exit status " << exit_status << " from test!" << std::endl;
- }
+
return exit_status;
} else {
std::cerr << "[----------] Non-normal exit from child!" << std::endl;
@@ -224,6 +224,54 @@ TEST_F(StorageTransactionsTest, ConcurentModification) {
ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems
}
- std::cout << "checking final result" << std::endl;
+ std::cout << "checking the final result" << std::endl;
EXPECT_EQ(t1->getRecord(5), -46);
}
+
+TEST_F(StorageTransactionsTest, RAIIResourceFree) {
+ EXPECT_EQ(db->ready(), true);
+
+ int pid = fork();
+ if (pid == 0) { // I am the child
+ std::cout << "beggining child transaction" << std::endl;
+ LMDBAL::WriteTransaction txn2 = db->beginTransaction(); //<--- this is where the execution should pause
+ //and wait for the first transaction to get finished
+ std::cout << "checking result of the parent transaction value" << std::endl;
+ EXPECT_FALSE(t1->checkRecord(221, txn2));
+
+ std::cout << "performing modification from the child thread" << std::endl;
+ t1->addRecord(221, 14, txn2);
+
+ std::cout << "commiting child transaction" << std::endl;
+ txn2.commit();
+
+ std::cout << "quitting child thread, letting child transaction be destroyed after commit" << std::endl;
+ exit(testing::Test::HasFailure());
+ } else { // I am the parent
+ std::cout << "beggining parent transaction" << std::endl;
+ {
+ LMDBAL::WriteTransaction txn1 = db->beginTransaction();
+
+ std::cout << "putting parent thread to sleep for 5 ms" << std::endl;
+ usleep(5);
+ std::cout << "parent thread woke up" << std::endl;
+
+ std::cout << "adding value from parent thread" << std::endl;
+ t1->addRecord(221, 320, txn1);
+
+ std::cout << "checking value from parent thread using transaction" << std::endl;
+ EXPECT_EQ(t1->getRecord(221, txn1), 320);
+
+ std::cout << "checking value independently from parent thread" << std::endl;
+ EXPECT_FALSE(t1->checkRecord(221));
+
+ std::cout << "implicitly aborting transaction by leaving the scope" << std::endl;
+ }
+
+ std::cout << "child thread should resume after this line" << std::endl;
+ ASSERT_EQ(0, waitForChildFork(pid)); //child process should have no problems
+ }
+
+ std::cout << "checking the final result" << std::endl;
+ EXPECT_EQ(t1->getRecord(221), 14);
+}