fix(Data): transactions are not handled properly #4230

This commit is contained in:
Alex Fabijanic 2023-10-27 17:45:39 +02:00
parent efd9b2ca1d
commit 439acf1924
12 changed files with 240 additions and 87 deletions

View File

@ -376,7 +376,7 @@ Poco::UInt32 SessionImpl::getDefaultTransactionIsolation() const
Poco::UInt32 SessionImpl::transactionIsolation(SQLULEN isolation)
{
if (0 == isolation)
throw InvalidArgumentException("transactionIsolation(SQLUINTEGER)");
throw InvalidArgumentException("transactionIsolation(0): invalid isolation 0");
Poco::UInt32 ret = 0;
@ -393,7 +393,7 @@ Poco::UInt32 SessionImpl::transactionIsolation(SQLULEN isolation)
ret |= Session::TRANSACTION_SERIALIZABLE;
if (0 == ret)
throw InvalidArgumentException("transactionIsolation(SQLUINTEGER)");
throw InvalidArgumentException(Poco::format("transactionIsolation(%u)", isolation));
return ret;
}
@ -401,6 +401,13 @@ Poco::UInt32 SessionImpl::transactionIsolation(SQLULEN isolation)
void SessionImpl::autoCommit(const std::string&, bool val)
{
if (val == isAutoCommit()) return;
if (val && isTransaction())
{
throw InvalidAccessException("autoCommit not "
"allowed for session in transaction");
}
checkError(Poco::Data::ODBC::SQLSetConnectAttr(_db,
SQL_ATTR_AUTOCOMMIT,
val ? (SQLPOINTER) SQL_AUTOCOMMIT_ON :

View File

@ -669,6 +669,9 @@ CppUnit::Test* ODBCDB2Test::suite()
CppUnit_addTest(pSuite, ODBCDB2Test, testMultipleResults);
CppUnit_addTest(pSuite, ODBCDB2Test, testSQLChannel);
CppUnit_addTest(pSuite, ODBCDB2Test, testSQLLogger);
CppUnit_addTest(pSuite, ODBCDB2Test, testAutoCommit);
CppUnit_addTest(pSuite, ODBCDB2Test, testSessionTransactionNoAutoCommit);
CppUnit_addTest(pSuite, ODBCDB2Test, testTransactionIsolation);
CppUnit_addTest(pSuite, ODBCDB2Test, testSessionTransaction);
CppUnit_addTest(pSuite, ODBCDB2Test, testTransaction);
CppUnit_addTest(pSuite, ODBCDB2Test, testTransactor);

View File

@ -494,6 +494,9 @@ CppUnit::Test* ODBCMySQLTest::suite()
//CppUnit_addTest(pSuite, ODBCMySQLTest, testMultipleResults);
CppUnit_addTest(pSuite, ODBCMySQLTest, testSQLChannel);
CppUnit_addTest(pSuite, ODBCMySQLTest, testSQLLogger);
CppUnit_addTest(pSuite, ODBCMySQLTest, testAutoCommit);
CppUnit_addTest(pSuite, ODBCMySQLTest, testSessionTransactionNoAutoCommit);
CppUnit_addTest(pSuite, ODBCMySQLTest, testTransactionIsolation);
CppUnit_addTest(pSuite, ODBCMySQLTest, testSessionTransaction);
CppUnit_addTest(pSuite, ODBCMySQLTest, testTransaction);
CppUnit_addTest(pSuite, ODBCMySQLTest, testTransactor);

View File

@ -931,6 +931,9 @@ CppUnit::Test* ODBCOracleTest::suite()
CppUnit_addTest(pSuite, ODBCOracleTest, testMultipleResults);
CppUnit_addTest(pSuite, ODBCOracleTest, testSQLChannel);
CppUnit_addTest(pSuite, ODBCOracleTest, testSQLLogger);
CppUnit_addTest(pSuite, ODBCOracleTest, testAutoCommit);
CppUnit_addTest(pSuite, ODBCOracleTest, testSessionTransactionNoAutoCommit);
CppUnit_addTest(pSuite, ODBCOracleTest, testTransactionIsolation);
CppUnit_addTest(pSuite, ODBCOracleTest, testAutoTransaction);
CppUnit_addTest(pSuite, ODBCOracleTest, testSessionTransaction);
CppUnit_addTest(pSuite, ODBCOracleTest, testTransaction);

View File

@ -671,6 +671,9 @@ CppUnit::Test* ODBCPostgreSQLTest::suite()
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testMultipleResults);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testSQLChannel);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testSQLLogger);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testAutoCommit);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testSessionTransactionNoAutoCommit);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testTransactionIsolation);
CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testSessionTransaction);
// (postgres bug?)
// local session claims to be capable of reading uncommitted changes,

View File

@ -966,6 +966,9 @@ CppUnit::Test* ODBCSQLServerTest::suite()
CppUnit_addTest(pSuite, ODBCSQLServerTest, testMultipleResults);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testSQLChannel);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testSQLLogger);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testAutoCommit);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testSessionTransactionNoAutoCommit);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testTransactionIsolation);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testSessionTransaction);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testTransaction);
CppUnit_addTest(pSuite, ODBCSQLServerTest, testTransactor);

View File

@ -388,6 +388,8 @@ CppUnit::Test* ODBCSQLiteTest::suite()
CppUnit_addTest(pSuite, ODBCSQLiteTest, testDynamicAny);
CppUnit_addTest(pSuite, ODBCSQLiteTest, testSQLChannel);
CppUnit_addTest(pSuite, ODBCSQLiteTest, testSQLLogger);
CppUnit_addTest(pSuite, ODBCSQLiteTest, testAutoCommit);
CppUnit_addTest(pSuite, ODBCSQLiteTest, testTransactionIsolation);
CppUnit_addTest(pSuite, ODBCSQLiteTest, testSessionTransaction);
CppUnit_addTest(pSuite, ODBCSQLiteTest, testTransaction);
CppUnit_addTest(pSuite, ODBCSQLiteTest, testTransactor);

View File

@ -1206,6 +1206,36 @@ void ODBCTest::testSQLLogger()
}
void ODBCTest::testAutoCommit()
{
if (!_pSession) fail ("Test not available.");
for (int i = 0; i < 8;)
{
recreatePersonTable();
_pSession->setFeature("autoBind", bindValue(i));
_pSession->setFeature("autoExtract", bindValue(i+1));
_pExecutor->autoCommit(_rConnectString);
i += 2;
}
}
void ODBCTest::testTransactionIsolation()
{
if (!_pSession) fail ("Test not available.");
for (int i = 0; i < 8;)
{
recreatePersonTable();
_pSession->setFeature("autoBind", bindValue(i));
_pSession->setFeature("autoExtract", bindValue(i+1));
_pExecutor->transactionIsolation(_rConnectString);
i += 2;
}
}
void ODBCTest::testSessionTransaction()
{
if (!_pSession) fail ("Test not available.");
@ -1221,6 +1251,21 @@ void ODBCTest::testSessionTransaction()
}
void ODBCTest::testSessionTransactionNoAutoCommit()
{
if (!_pSession) fail ("Test not available.");
for (int i = 0; i < 8;)
{
recreatePersonTable();
_pSession->setFeature("autoBind", bindValue(i));
_pSession->setFeature("autoExtract", bindValue(i+1));
_pExecutor->sessionTransactionNoAutoCommit(_rConnectString);
i += 2;
}
}
void ODBCTest::testTransaction()
{
if (!_pSession) fail ("Test not available.");

View File

@ -152,7 +152,10 @@ public:
virtual void testSQLChannel();
virtual void testSQLLogger();
virtual void testAutoCommit();
virtual void testTransactionIsolation();
virtual void testSessionTransaction();
virtual void testSessionTransactionNoAutoCommit();
virtual void testTransaction();
virtual void testTransactor();
virtual void testNullable();

View File

@ -4172,40 +4172,23 @@ void SQLExecutor::setTransactionIsolation(Session& session, Poco::UInt32 ti)
}
void SQLExecutor::sessionTransaction(const std::string& connect)
void SQLExecutor::autoCommit(const std::string& connect)
{
if (!session().canTransact())
{
std::cout << "Session not capable of transactions." << std::endl;
return;
}
Session local("odbc", connect);
std::string funct = "sessionTransaction()";
std::vector<std::string> lastNames;
std::vector<std::string> firstNames;
std::vector<std::string> addresses;
std::vector<int> ages;
std::string tableName("Person");
lastNames.push_back("LN1");
lastNames.push_back("LN2");
firstNames.push_back("FN1");
firstNames.push_back("FN2");
addresses.push_back("ADDR1");
addresses.push_back("ADDR2");
ages.push_back(1);
ages.push_back(2);
int count = 0, locCount = 0;
std::string result;
bool autoCommit = session().getFeature("autoCommit");
session().setFeature("autoCommit", true);
assertTrue (!session().isTransaction());
session().setFeature("autoCommit", false);
assertTrue (!session().isTransaction());
session().setFeature("autoCommit", true);
assertTrue (!session().isTransaction());
session().setFeature("autoCommit", autoCommit);
}
void SQLExecutor::transactionIsolation(const std::string& connect)
{
auto ti = session().getTransactionIsolation();
// these are just calls to check the transactional capabilities of the session
@ -4215,18 +4198,52 @@ void SQLExecutor::sessionTransaction(const std::string& connect)
setTransactionIsolation(session(), Session::TRANSACTION_SERIALIZABLE);
setTransactionIsolation(session(), Session::TRANSACTION_READ_COMMITTED);
setTransactionIsolation(session(), ti);
}
void SQLExecutor::sessionTransaction(const std::string& connect)
{
if (!session().canTransact())
{
std::cout << "Session not capable of transactions." << std::endl;
return;
}
bool autoCommit = session().getFeature("autoCommit");
Session local("odbc", connect);
std::string funct = "sessionTransaction()";
std::string tableName("Person");
std::vector<std::string> lastNames = {"LN1", "LN2"};
std::vector<std::string> firstNames = {"FN1", "FN2"};
std::vector<std::string> addresses = {"ADDR1", "ADDR2"};
std::vector<int> ages = {1, 2};
int count = 0, locCount = 0;
std::string result;
session().setFeature("autoCommit", true);
session().begin();
assertTrue (session().isTransaction());
try { session() << "INSERT INTO Person VALUES (?,?,?,?)", use(lastNames), use(firstNames), use(addresses), use(ages), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
// autocommit is invalid for session in transaction ...
try
{
session().setFeature("autoCommit", true);
fail ("must fail on autocommit setting during transaction");
}
catch(const Poco::InvalidAccessException& e) { }
// but setting it to its current state is allowed (no-op)
session().setFeature("autoCommit", false);
session() << "INSERT INTO Person VALUES (?,?,?,?)", use(lastNames), use(firstNames), use(addresses), use(ages), now;
assertTrue (session().isTransaction());
Statement stmt = (local << "SELECT COUNT(*) FROM Person", into(locCount), async, now);
try { session() << "SELECT COUNT(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT COUNT(*) FROM Person", into(count), now;
assertTrue (2 == count);
assertTrue (session().isTransaction());
session().rollback();
@ -4234,35 +4251,111 @@ void SQLExecutor::sessionTransaction(const std::string& connect)
stmt.wait();
assertTrue (0 == locCount);
stmt.reset(session());
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (0 == count);
assertTrue (!session().isTransaction());
session().begin();
try { session() << "INSERT INTO Person VALUES (?,?,?,?)", use(lastNames), use(firstNames), use(addresses), use(ages), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "INSERT INTO Person VALUES (?,?,?,?)", use(lastNames), use(firstNames), use(addresses), use(ages), now;
assertTrue (session().isTransaction());
Statement stmt1 = (local << "SELECT COUNT(*) FROM Person", into(locCount), async, now);
stmt = (local << "SELECT COUNT(*) FROM Person", into(locCount), async, now);
session().commit();
assertTrue (!session().isTransaction());
stmt1.wait();
stmt.wait();
assertTrue (2 == locCount);
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (2 == count);
// end autoCommit = true
// restore the original transaction state
session().setFeature("autoCommit", autoCommit);
setTransactionIsolation(session(), ti);
}
void SQLExecutor::sessionTransactionNoAutoCommit(const std::string& connect)
{
bool autoCommit = session().getFeature("autoCommit");
Session local("odbc", connect);
local.setFeature("autoCommit", false);
assertTrue (!local.getFeature("autoCommit"));
if (!local.canTransact())
{
std::cout << "Session not capable of transactions." << std::endl;
return;
}
std::string funct = "sessionTransactionNoAutoCommit()";
std::vector<std::string> lastNames = {"LN1", "LN2"};
std::vector<std::string> firstNames = {"FN1", "FN2"};
std::vector<std::string> addresses = {"ADDR1", "ADDR2"};
std::vector<int> ages = {1, 2};
std::string tableName("Person");
int count = 0, locCount = 0;
std::string result;
// no autoCommit session becomes transaction without explicit begin()
assertTrue (!local.isTransaction());
assertTrue (!session().isTransaction());
local << "INSERT INTO Person VALUES (?,?,?,?)",
use(lastNames), use(firstNames), use(addresses), use(ages), now;
Statement stmt = (session() << "SELECT COUNT(*) FROM Person",
into(count), async, now);
local << "SELECT COUNT(*) FROM Person", into(locCount), now;
assertTrue (0 == count);
assertTrue (2 == locCount);
assertTrue (local.isTransaction());
// autocommit is invalid for session in transaction ...
try
{
local.setFeature("autoCommit", true);
fail ("must fail on autocommit setting during transaction");
}
catch(const Poco::InvalidAccessException& e) { }
// but setting it to its current state is allowed (no-op)
local.setFeature("autoCommit", false);
assertTrue (!session().isTransaction());
local.commit();
assertTrue (!local.isTransaction());
stmt.wait();
assertTrue (2 == count);
count = 0;
stmt.reset(session());
assertTrue (!local.isTransaction());
assertTrue (!session().isTransaction());
local << "INSERT INTO Person VALUES (?,?,?,?)",
use(lastNames), use(firstNames), use(addresses), use(ages), now;
stmt = (session() << "SELECT COUNT(*) FROM Person", into(count), async, now);
local << "SELECT COUNT(*) FROM Person", into(locCount), now;
assertTrue (0 == count);
assertTrue (4 == locCount);
assertTrue (local.isTransaction());
assertTrue (!session().isTransaction());
local.rollback();
assertTrue (!local.isTransaction());
stmt.wait();
assertTrue (2 == count);
locCount = 0;
session() << "SELECT COUNT(*) FROM Person", into(count), now;
local << "SELECT COUNT(*) FROM Person", into(locCount), now;
assertTrue (2 == count);
assertTrue (2 == locCount);
session().setFeature("autoCommit", autoCommit);
}
@ -4287,19 +4380,11 @@ void SQLExecutor::transaction(const std::string& connect)
setTransactionIsolation(local, Session::TRANSACTION_READ_COMMITTED);
std::string funct = "transaction()";
std::vector<std::string> lastNames;
std::vector<std::string> firstNames;
std::vector<std::string> addresses;
std::vector<int> ages;
std::string tableName("Person");
lastNames.push_back("LN1");
lastNames.push_back("LN2");
firstNames.push_back("FN1");
firstNames.push_back("FN2");
addresses.push_back("ADDR1");
addresses.push_back("ADDR2");
ages.push_back(1);
ages.push_back(2);
std::vector<std::string> lastNames = {"LN1", "LN2"};
std::vector<std::string> firstNames = {"FN1", "FN2"};
std::vector<std::string> addresses = {"ADDR1", "ADDR2"};
std::vector<int> ages = {1, 2};
int count = 0, locCount = 0;
std::string result;
@ -4334,7 +4419,8 @@ void SQLExecutor::transaction(const std::string& connect)
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
assertTrue (0 == count);
assertTrue (!session().isTransaction());
assertTrue (session().isTransaction());
session().commit();
{
Transaction trans(session());
@ -4394,14 +4480,10 @@ void SQLExecutor::transaction(const std::string& connect)
Transaction trans(session());
trans.execute(sql1, false);
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (1 == count);
trans.execute(sql2, false);
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (2 == count);
Statement stmt2 = (local << "SELECT COUNT(*) FROM Person", into(locCount), async, now);
@ -4425,6 +4507,7 @@ void SQLExecutor::transaction(const std::string& connect)
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
assertTrue (2 == count);
session().commit();
// restore the original transaction state
session().setFeature("autoCommit", autoCommit);
@ -4457,24 +4540,20 @@ void SQLExecutor::transactor()
int count = 0;
bool autoCommit = session().getFeature("autoCommit");
auto ti = session().getTransactionIsolation();
session().setFeature("autoCommit", false);
session().setTransactionIsolation(Session::TRANSACTION_READ_COMMITTED);
TestCommitTransactor ct;
Transaction t1(session(), ct);
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (1 == count);
try { session() << "DELETE FROM Person", now; session().commit();}
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "DELETE FROM Person", now; session().commit();
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (0 == count);
try
@ -4484,9 +4563,7 @@ void SQLExecutor::transactor()
fail ("must fail");
} catch (Poco::Exception&) { }
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (0 == count);
try
@ -4497,9 +4574,7 @@ void SQLExecutor::transactor()
fail ("must fail");
} catch (Poco::Exception&) { }
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (0 == count);
try
@ -4510,9 +4585,7 @@ void SQLExecutor::transactor()
fail ("must fail");
} catch (Poco::Exception&) { }
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (0 == count);
try
@ -4523,12 +4596,13 @@ void SQLExecutor::transactor()
fail ("must fail");
} catch (Poco::Exception&) { }
try { session() << "SELECT count(*) FROM Person", into(count), now; }
catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
session() << "SELECT count(*) FROM Person", into(count), now;
assertTrue (0 == count);
session().commit();
// restore the original transaction state
session().setFeature("autoCommit", autoCommit);
setTransactionIsolation(session(), ti);
}

View File

@ -511,7 +511,11 @@ public:
void sqlChannel(const std::string& connect);
void sqlLogger(const std::string& connect);
void autoCommit(const std::string& connect);
void transactionIsolation(const std::string& connect);
void sessionTransaction(const std::string& connect);
void sessionTransactionNoAutoCommit(const std::string& connect);
void transaction(const std::string& connect);
void transactor();
void nullable();

View File

@ -201,7 +201,10 @@ public:
Statement operator << (const T& t)
/// Creates a Statement with the given data as SQLContent
{
return _statementCreator << t;
Statement stmt = (_statementCreator << t);
if (!_pImpl->isTransaction() && !isAutocommit())
_pImpl->begin();
return stmt;
}
SharedPtr<StatementImpl> createStatementImpl();