Set autocommit off in PostgreSQL with SOCI C++ - c++

This is an answer rather than a question which I need to state in SO anyway. I was struggle with this question ("how to turn off autocommit when using soci library with PostgreSQL databases") for a long time and came up with several solutions.
In Oracle, by default the auto commit option is turned off and we have to call soci::session::commit explicitly to commit the transactions we have made but in PostgreSQL this is other way around and it will commit as soon as we execute a sql statement (correct me, if I'm wrong). This will introduce problems when we write applications database independently. The soci library provide soci::transaction in order to address this.
So, when we initialize a soci::transaction by providing the soci::session to that, it will hold the transaction we have made without commiting to the database. At the end when we call soci::transaction::commit it will commit the changes to the database.
soci::session sql(CONNECTION_STRING);
soci::transaction tr(sql);
try {
sql << "insert into soci_test(id, name) values(7, \'John\')";
tr.commit();
}
catch (std::exception& e) {
tr.rollback();
}
But, performing commit or rollback will end the transaction tr and we need to initialize another soci::transaction in order to hold future transactions (to create an active in progress transaction) we are about to make. Here are more fun facts about soci::transaction.
You can have only one soci::transaction instance per soci::session. The second one will replace the first one, if you initialize another.
You can not perform more than a single commit or rollback using a soci::transaction. You will receive an exception, at the second time you do commit or rollback.
You can initialize a transaction, then use session::commit or session::rollback. It will give the same result as transaction::commit or transaction::rollback. But the transaction will end as soon as you perform single commit or rollback as usual.
It doesn't matter the visibility of the soci::transaction object to your scope (where you execute the sql and call commit or rollback) in order to hold the db transactions you made until explicitly commit or rollback. In other words, if there is an active transaction in progress for a session, db transactions will hold until we explicitly commit or rollback.
But, if the lifetime of the transaction instance which created for the session was end, we cannot expect the db transactions will be halt.
If you every suffer with "WARNING: there is no transaction in progress", you have to perform commit or rollback only using soci::transaction::commit or soci::transaction::rollback.
Now I will post the solution which I came up with, in order to enable the explicit commit or rollback with any database backend.

This is the solution I came up with.
namespace mysociutils
{
class session : public soci::session
{
public:
void open(std::string const & connectString)
{
soci::session::open(connectString);
tr = std::unique_ptr<soci::transaction>(new soci::transaction(*this));
}
void commit()
{
tr->commit();
tr = std::unique_ptr<soci::transaction>(new soci::transaction(*this));
}
void rollback()
{
tr->rollback();
tr = std::unique_ptr<soci::transaction>(new soci::transaction(*this));
}
void ~session()
{
tr->rollback();
}
private:
std::unique_ptr<soci::transaction> tr;
};
}
When ever commit or rollback is performed, initialize a new soci::transaction. Now you can replace your soci::session sql with mysociutils::session sql and enjoy SET AUTOCOMMIT OFF.

Related

How to start an immediate transaction of Sqlite in Qt?

Got troubles with using QSqlDatabase::transaction().
I want to start a transaction of Sqlite in Qt and I know this function : bool QSqlDatabase::transaction():
if(db.transaction())
{
QSqlQuery query(db);
// do stuff
if(!db.commit())
{
qDebug() << "Failed to commit";
db.rollback();
}
}
My problem is that I want to start an immediate transaction as follows:
> begin immediate;
> ...(operation)
> commit;
This function bool QSqlDatabase::transaction() will start a default transaction.
There will be several users using this program to access sqlite database so I need to control their access orders. If one user is writing, others can't read or write. It seems that I need to start an immediate transaction.
So how to start an immediate transaction by QSqlDatabase::transaction() ?
Is there way to specify transaction type in QSqlDatabase::transaction() ?
I use this to start an immediate transaction:
QsqlQuery query(myDatabase);
query.exec("begin immediate;");
query.exec(...) // read or write database.
query.exec("commit;");
It didn't work. There is still wrong data caused by multiple user access in database.

How avoid closing EntityManager when OptimisticLockException occurs?

My problem - process try change entity that already changed and have newest version id. When i do flush() in my code in UnitOfWork's commit() rising OptimisticLockException and catching in same place by catch-all block. And in this catch doctrine closing EntityManager.
If i want skip this entity and continue with another from ArrayCollection, i should not use flush()?
Try recreate EntityManager:
}catch (OptimisticLockException $e){
$this->em = $this->container->get('doctrine')->getManager();
echo "\n||OptimisticLockException.";
continue;
}
And still get
[Doctrine\ORM\ORMException]
The EntityManager is closed.
Strange.
If i do
$this->em->lock($entity, LockMode::OPTIMISTIC, $entity->getVersion());
and then do flush() i get OptimisticLockException and closed entity manager.
if i do
$this->getContainer()->get('doctrine')->resetManager();
$em = $doctrine->getManager();
Old data unregistered with this entity manager and i even can't write logs in database, i get error:
[Symfony\Component\Debug\Exception\ContextErrorException]
Notice: Undefined index: 00000000514cef3c000000002ff4781e
You should check entity version before you try to flush it to avoid exception. In other words you should not call flush() method if the lock fails.
You can use EntityManager#lock() method for checking whether you can flush entity or not.
/** #var EntityManager $em */
$entity = $em->getRepository('Post')->find($_REQUEST['id']);
// Get expected version (easiest way is to have the version number as a hidden form field)
$expectedVersion = $_REQUEST['version'];
// Update your entity
$entity->setText($_REQUEST['text']);
try {
//assert you edit right version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
//if $em->lock() fails flush() is not called and EntityManager is not closed
$em->flush();
} catch (OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
Check the example in Doctrine docs optimistic locking
Unfortunately, nearly 4 years later, Doctrine is still unable to recover from an optimistic lock properly.
Using the lock function as suggested in the doc doesn't work if the db was changed by another server or php worker thread. The lock function only makes sure the version number wasn't changed by the current php script since the entity was loaded into memory. It doesn't read the db to make sure the version number is still the expected one.
And even if it did read the db, there is still the potential for a race condition between the time the lock function checks the current version in the db and the flush is performed.
Consider this scenario:
server A reads the entity,
server B reads the same entity,
server B updates the db,
server A updates the db <== optimistic lock exception
The exception is triggered when flush is called and there is nothing that can be done to prevent it.
Even a pessimistic lock won't help unless you can afford to loose performance and actually lock your db for a (relatively) long time.
Doctrine's solution (update... where version = :expected_version) is good in theory. But, sadly, Doctrine was designed to become unusable once an exception is triggered. Any exception. Every entity is detached. Even if the optimistic lock can be easily solved by re-reading the entity and applying the change again, Doctrine makes it very hard to do so.
As others have said, sometimes EntityManager#lock() is not useful. In my case, the Entity version may change during the same request.
If EntityManager closes after flush(), I proceed like this:
if (!$entityManager->isOpen()) {
$entityManager = EntityManager::create(
$entityManager->getConnection(),
$entityManager->getConfiguration(),
$entityManager->getEventManager()
);
// ServiceManager shoud be aware of this change
// this is for Zend ServiceManager your shoud adapt this part to your usecase
$serviceManager = $application->getServiceManager();
$serviceManager->setAllowOverride(true);
$serviceManager->setService(EntityManager::class, $entityManager);
$serviceManager->setAllowOverride(false);
// Then you should manually reload every Entity you need (or repeat the whole set of actions)
}

NHibernate Load vs. Get behavior for testing

In simple tests I can assert whether an object has been persisted by whether it's Id is no longer at it's default value. But delete an object and want to check whether the object and perhaps its children are really not in the database, the object Id's will still be at their saved values.
So I need to go to the db, and I would like a helper assertion to make the tests more readable, which is where the question comes in. I like the idea of using Load to save the db call, but I'm wondering if the ensuing exceptions can corrupt the session.
Below are how the two assertions would look, I think. Which would you use?
Cheers,
Berryl
Get
public static void AssertIsTransient<T>(this T instance, ISession session)
where T : Entity
{
if (instance.IsTransient()) return;
var found = session.Get<T>(instance.Id);
if (found != null) Assert.Fail(string.Format("{0} has persistent id '{1}'", instance, instance.Id));
}
Load
public static void AssertIsTransient<T>(this T instance, ISession session)
where T : Entity
{
if (instance.IsTransient()) return;
try
{
var found = session.Load<T>(instance.Id);
if (found != null) Assert.Fail(string.Format("{0} has persistent id '{1}'", instance, instance.Id));
}
catch (GenericADOException)
{
// nothing
}
catch (ObjectNotFoundException)
{
// nothing
}
}
edit
In either case I would be doing the fetch (Get or Load) in a new session, free of state from the session that did the save or delete.
I am trying to test cascade behavior, NOT to test NHib's ability to delete things, but maybe I am over thinking this one or there is a simpler way I haven't thought of.
Your code in the 'Load'-section will always hit Assert.Fail, but never throw an exception as Load<T> will return a proxy (with the Id-property set - or populated from the 1st level cache) without hitting the DB - ie. ISession.Load will only fail, if you access a property other than your Id-property on a deleted entity.
As for your 'Get'-section - I might be mistaken, but I think that if you delete an entity in a session - and later try to use .Get in the same session - you will get the one in 1st level cache - and again not return null.
See this post for the full explanation about .Load and .Get.
If you really need to see if it is in your DB - use a IStatelessSession - or launch a child-ISession (which will have an empty 1st level cache.
EDIT: I thought of a bigger problem - your entity will first be deleted when the transaction is committed (when the session is flushed per default) - so unless you manually flush your session (not recommended), you will still have it in your DB.
Hope this helps.

JPA - only first commit failed, but should failed all

please can somebody help me to explain the following (for me) very strange JPA behaviour. I intentionally change primary key of entity which is prohibed in JPA.
So first commit correctly throws "Exception Description: The attribute [date] of class [some.package.Holiday] is mapped to a primary key column in the database. Updates are not allowed.".
But second (third, fourth, ...) succeed...! How is this possible?!
Holiday h1 = EM.find(Holiday.class, new GregorianCalendar(2011, 0, 3).getTime());
try {
EM.getTransaction().begin();
h1.setDate(new GregorianCalendar(2011, 0, 4).getTime());
EM.getTransaction().commit();
System.out.println("First commit succeed");
} catch (Exception e) {
System.out.println("First commit failed");
}
try {
EM.getTransaction().begin();
EM.getTransaction().commit();
System.out.println("Second commit succeed");
} catch (Exception e) {
System.out.println("Second commit failed");
}
It will printout:
First commit failed
Second commit succeed
OMG, how is this possible?!
(Using EclipseLink 2.2.0.v20110202-r8913 with MySQL.)
The failure of the commit operation for the first transaction has no bearing on the second transaction. This is due to the fact that when the first commit fails, the EntityTransaction is no longer in the active state. When you issue the second em.getTransaction().begin invocation, a new transaction is initiated that does not have any knowledge of the first.
It is important to note that although your code may use the same EntityTransaction reference in both cases, it is not necessary that this class actually represent the transaction. In the case of EclipseLink, the EntityTransaction reference actually wraps an EntityTransactionWrapper instance that further uses a RepeatableUnitOfWork, the latter two classes being provided by EclipseLink implementation and not JPA. It is the RepeatableWriteUnitOfWork instance that actually tracks the collection of changes made to entities that will be merged into the shared cache (and the database). When the first transaction fails, the underlying UnitOfWork is invalidated, and new UnitOfWork is established when you start the second EntityTransaction.
The same will apply to most other JPA providers as the EntityTransaction class is not a concrete final class. Instead, it is an interface that is typically implemented by another class in the JPA provider, and which may likewise wrap a transaction thereby requiring clients to use the EntityTransaction reference instead of directly working with the underlying transaction (which may be a JTA transaction or a resource-local transaction).
Additionally, you ought to remember that:
EntityTransaction.begin() should be invoked only once. Invoking it a second time will result in an IllegalStateException exception being thrown as it cannot be invoked when a transaction is active. So, the fact that you are able to invoke it the second time, implies that the first transaction is no longer active.
If you require the changes performed in the context of the first transaction to be made available to the second, you must merge the entities back into the shared context in the second transaction, after they've been detached by the first. While, this may sound ridiculous, you ought to remember that detached entities can be modified by clients (read, end-users) before they are merged back, so the changes made by the end users may be retained, while mistakes (like the modification of the primary keys) may be corrected in the interim.

TransactionScope with SQLite in-memory database and NHibernate

I'm running into a problem where a transaction does not roll back when using TransactionScope.
We're using NHibernate with an in memory SQLite database, so that limits us to just one db connection for the duration of the entire lifetime of the application (in this case, some unit tests).
using (var ts = new TransactionScope(TransactionScopeOption.Required,
TimeSpan.Zero))
{
using (var transaction = _repository.BeginTransaction())
{
_repository.Save(entity);
transaction.Commit();
}
// ts.Complete(); <- commented Complete call still commits transaction
}
Even if I remove NHibernate's inner nested transaction so the code is simply as below, the transaction is still commited.
using (var ts = new TransactionScope(TransactionScopeOption.Required,
TimeSpan.Zero))
{
_repository.Save(entity);
} // no Complete(), but the transaction still commits
Is it expecting a freshly opened SQLite connection inside the TransactionScope block in order to enlist it in the transaction?
Again, I can't supply it with a new connection because that would clear out the database.
Using NHibernate 3.0 and SQLite 1.0.66.0, both latest versions at the time of writing.
Note: using transaction.Rollback() on the NHibernate ITransaction object correctly rolls back the transaction, it's just the TransactionScope support that doesn't seem to work.
I think I may have found the reason for this. If the connection is not opened from inside the TransactionScope block, it will not be enlisted in the transaction.
There's some information here:
http://msdn.microsoft.com/en-us/library/aa720033(v=vs.71).aspx
Solution:
I already had a .BeginTransaction() method in my repository, so I figured I'd manually enlist the connection in the ambient transaction there.
This is the code I ended up with:
/// <summary>
/// Begins an explicit transaction.
/// </summary>
/// <returns></returns>
public ITransaction BeginTransaction()
{
if (System.Transactions.Transaction.Current != null)
{
((DbConnection) Session.Connection).EnlistTransaction(System.Transactions.Transaction.Current);
}
return Session.BeginTransaction();
}
And here's how I'm using it:
using (var ts = new TransactionScope(TransactionScopeOption.Required, TimeSpan.Zero))
using (var transaction = repository.BeginTransaction())
{
repository.Save(entity);
transaction.Commit(); // nhibernate transaction is commited
// ts.Complete(); // TransactionScope is not commited
} // transaction is correctly rolled back now