mirror of
https://github.com/pocoproject/poco.git
synced 2025-04-03 01:54:47 +02:00
Serializable Isolation level for SQLite Databases (#4693)
* fix #4536 I've add transaction isolation support for SQLite Warning! SQLite transactions are [serializable by design](https:// www.sqlite.org/isolation.html) but my implementation retuns false in function hasTransactionIsolation and throw an exception in setTransactionIsolation * disable shared cache in TearDown() * chore(doc): reword documentation * we should waitresult of select before rollback (#4718) --------- Co-authored-by: Alexander B <ale.bychuk@gmail.com>
This commit is contained in:
parent
eaabd3ff8d
commit
88c4958032
Data/SQLite
@ -100,6 +100,12 @@ public:
|
||||
|
||||
void setTransactionIsolation(Poco::UInt32 ti);
|
||||
/// Sets the transaction isolation level.
|
||||
/// Warning! In order to use TRANSACTION_READ_UNCOMMITTED isolation level, it is important
|
||||
/// to keep in mind that enableSharedCache() and setThreadMode() affect connection lock behavior.
|
||||
/// Eg. TRANSACTION_READ_UNCOMMITTED with enableSharedCache() and setThreadMode(THREAD_MODE_MULTI)
|
||||
/// in a multiple thread, shared connection and custom code for table-lock exceptions scenario,
|
||||
/// multiple connections on the same thread with TRANSACTION_READ_UNCOMMITTED will throw
|
||||
/// "database locked" exception.
|
||||
|
||||
Poco::UInt32 getTransactionIsolation() const;
|
||||
/// Returns the transaction isolation level.
|
||||
@ -144,9 +150,12 @@ private:
|
||||
Poco::Mutex _mutex;
|
||||
static const std::string DEFERRED_BEGIN_TRANSACTION;
|
||||
static const std::string EXCLUSIVE_BEGIN_TRANSACTION;
|
||||
static const std::string IMMEDIATE_BEGIN_TRANSACTION;
|
||||
static const std::string IMMEDIATE_BEGIN_TRANSACTION;
|
||||
static const std::string COMMIT_TRANSACTION;
|
||||
static const std::string ABORT_TRANSACTION;
|
||||
static const std::string SQLITE_READ_UNCOMMITTED;
|
||||
static const std::string SQLITE_READ_COMMITTED;
|
||||
Poco::UInt32 _transactionIsolationLevel;
|
||||
};
|
||||
|
||||
|
||||
|
@ -44,6 +44,9 @@ const std::string SessionImpl::EXCLUSIVE_BEGIN_TRANSACTION("BEGIN EXCLUSIVE");
|
||||
const std::string SessionImpl::IMMEDIATE_BEGIN_TRANSACTION("BEGIN IMMEDIATE");
|
||||
const std::string SessionImpl::COMMIT_TRANSACTION("COMMIT");
|
||||
const std::string SessionImpl::ABORT_TRANSACTION("ROLLBACK");
|
||||
const std::string SessionImpl::SQLITE_READ_UNCOMMITTED = "PRAGMA read_uncommitted = true";
|
||||
const std::string SessionImpl::SQLITE_READ_COMMITTED = "PRAGMA read_uncommitted = false";
|
||||
|
||||
|
||||
|
||||
SessionImpl::SessionImpl(const std::string& fileName, std::size_t loginTimeout):
|
||||
@ -52,7 +55,8 @@ SessionImpl::SessionImpl(const std::string& fileName, std::size_t loginTimeout):
|
||||
_pDB(0),
|
||||
_connected(false),
|
||||
_isTransaction(false),
|
||||
_transactionType(TransactionType::DEFERRED)
|
||||
_transactionType(TransactionType::DEFERRED),
|
||||
_transactionIsolationLevel(Session::TRANSACTION_READ_COMMITTED)
|
||||
{
|
||||
open();
|
||||
setConnectionTimeout(loginTimeout);
|
||||
@ -128,28 +132,57 @@ void SessionImpl::rollback()
|
||||
|
||||
void SessionImpl::setTransactionIsolation(Poco::UInt32 ti)
|
||||
{
|
||||
if (ti != Session::TRANSACTION_READ_COMMITTED)
|
||||
throw Poco::InvalidArgumentException("setTransactionIsolation()");
|
||||
Poco::Mutex::ScopedLock l(_mutex);
|
||||
SQLiteStatementImpl tmp(*this, _pDB);
|
||||
switch (ti)
|
||||
{
|
||||
case Session::TRANSACTION_READ_COMMITTED:
|
||||
tmp.add(SQLITE_READ_COMMITTED);
|
||||
_transactionIsolationLevel = Session::TRANSACTION_READ_COMMITTED;
|
||||
break;
|
||||
case Session::TRANSACTION_READ_UNCOMMITTED:
|
||||
tmp.add(SQLITE_READ_UNCOMMITTED);
|
||||
_transactionIsolationLevel = Session::TRANSACTION_READ_UNCOMMITTED;
|
||||
break;
|
||||
case Session::TRANSACTION_REPEATABLE_READ:
|
||||
throw Poco::InvalidArgumentException("setTransactionIsolation(TRANSACTION_REPEATABLE_READ) - unsupported");
|
||||
case Session::TRANSACTION_SERIALIZABLE:
|
||||
throw Poco::InvalidArgumentException("setTransactionIsolation(TRANSACTION_SERIALIZABLE) - unsupported [SQLite transactions are serializable by design]");
|
||||
default:
|
||||
throw Poco::InvalidArgumentException(Poco::format("setTransactionIsolation(%u) - unsupported", ti));
|
||||
break;
|
||||
}
|
||||
tmp.execute();
|
||||
}
|
||||
|
||||
|
||||
Poco::UInt32 SessionImpl::getTransactionIsolation() const
|
||||
{
|
||||
return Session::TRANSACTION_READ_COMMITTED;
|
||||
return _transactionIsolationLevel;
|
||||
}
|
||||
|
||||
|
||||
bool SessionImpl::hasTransactionIsolation(Poco::UInt32 ti) const
|
||||
{
|
||||
if (ti == Session::TRANSACTION_READ_COMMITTED) return true;
|
||||
switch (ti)
|
||||
{
|
||||
case Session::TRANSACTION_READ_COMMITTED:
|
||||
return true;
|
||||
case Session::TRANSACTION_READ_UNCOMMITTED:
|
||||
return true;
|
||||
case Session::TRANSACTION_REPEATABLE_READ:
|
||||
return false;
|
||||
case Session::TRANSACTION_SERIALIZABLE:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool SessionImpl::isTransactionIsolation(Poco::UInt32 ti) const
|
||||
{
|
||||
if (ti == Session::TRANSACTION_READ_COMMITTED) return true;
|
||||
return false;
|
||||
return getTransactionIsolation() == ti;
|
||||
}
|
||||
|
||||
|
||||
|
@ -89,6 +89,7 @@ using Poco::Int64;
|
||||
using Poco::Data::SQLite::Utility;
|
||||
using Poco::delegate;
|
||||
using Poco::Stopwatch;
|
||||
using Poco::Data::SQLite::Connector;
|
||||
|
||||
|
||||
class Person
|
||||
@ -3144,7 +3145,7 @@ void SQLiteTest::setTransactionIsolation(Session& session, Poco::UInt32 ti)
|
||||
}
|
||||
|
||||
|
||||
void SQLiteTest::testSessionTransaction()
|
||||
void SQLiteTest::testSessionTransactionReadCommitted()
|
||||
{
|
||||
Session session (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
||||
assertTrue (session.isConnected());
|
||||
@ -3222,10 +3223,6 @@ void SQLiteTest::testSessionTransaction()
|
||||
session << "SELECT count(*) FROM Person", into(count), now;
|
||||
assertTrue (2 == count);
|
||||
|
||||
/* TODO: see http://www.sqlite.org/pragma.html#pragma_read_uncommitted
|
||||
setTransactionIsolation(session, Session::TRANSACTION_READ_UNCOMMITTED);
|
||||
*/
|
||||
|
||||
session.close();
|
||||
assertTrue (!session.isConnected());
|
||||
|
||||
@ -3233,7 +3230,108 @@ void SQLiteTest::testSessionTransaction()
|
||||
assertTrue (!local.isConnected());
|
||||
}
|
||||
|
||||
void SQLiteTest::testSessionTransactionSerializable()
|
||||
{
|
||||
Session session (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
||||
assertTrue (session.isConnected());
|
||||
setTransactionIsolation(session, Session::TRANSACTION_SERIALIZABLE);
|
||||
}
|
||||
|
||||
void SQLiteTest::testSessionTransactionRepeatableRead()
|
||||
{
|
||||
Session session (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
||||
assertTrue (session.isConnected());
|
||||
setTransactionIsolation(session, Session::TRANSACTION_REPEATABLE_READ);
|
||||
}
|
||||
|
||||
void SQLiteTest::testSessionTransactionReadUncommitted()
|
||||
{
|
||||
Connector::enableSharedCache();
|
||||
Session session (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
||||
assertTrue (session.isConnected());
|
||||
|
||||
session << "DROP TABLE IF EXISTS Person", now;
|
||||
session << "CREATE TABLE IF NOT EXISTS Person (LastName VARCHAR(30), FirstName VARCHAR, Address VARCHAR, Age INTEGER(3))", now;
|
||||
|
||||
if (!session.canTransact())
|
||||
{
|
||||
std::cout << "Session not capable of transactions." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
Session local (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
||||
assertTrue (local.isConnected());
|
||||
|
||||
assertTrue (local.getFeature("autoCommit"));
|
||||
|
||||
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);
|
||||
int count = 0, locCount = 0;
|
||||
std::string result;
|
||||
|
||||
setTransactionIsolation(session, Session::TRANSACTION_READ_UNCOMMITTED);
|
||||
setTransactionIsolation(local, Session::TRANSACTION_READ_UNCOMMITTED);
|
||||
|
||||
session.begin();
|
||||
assertTrue (!session.getFeature("autoCommit"));
|
||||
assertTrue (session.isTransaction());
|
||||
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);
|
||||
|
||||
session << "SELECT COUNT(*) FROM Person", into(count), now;
|
||||
assertTrue (2 == count);
|
||||
|
||||
stmt.wait();
|
||||
assertTrue (session.isTransaction());
|
||||
session.rollback();
|
||||
|
||||
assertTrue (!session.isTransaction());
|
||||
assertTrue (session.getFeature("autoCommit"));
|
||||
|
||||
assertEqual(2, locCount);
|
||||
|
||||
session << "SELECT count(*) FROM Person", into(count), now;
|
||||
assertTrue (0 == count);
|
||||
assertTrue (!session.isTransaction());
|
||||
|
||||
session.begin();
|
||||
session << "INSERT INTO Person VALUES (?,?,?,?)", use(lastNames), use(firstNames), use(addresses), use(ages), now;
|
||||
assertTrue (session.isTransaction());
|
||||
assertTrue (!session.getFeature("autoCommit"));
|
||||
|
||||
Statement stmt1 = (local << "SELECT COUNT(*) FROM Person", into(locCount), now);
|
||||
assertTrue (2 == locCount);
|
||||
|
||||
session << "SELECT count(*) FROM Person", into(count), now;
|
||||
assertTrue (2 == count);
|
||||
|
||||
session.commit();
|
||||
assertTrue (!session.isTransaction());
|
||||
assertTrue (session.getFeature("autoCommit"));
|
||||
|
||||
session << "SELECT count(*) FROM Person", into(count), now;
|
||||
assertTrue (2 == count);
|
||||
|
||||
session.close();
|
||||
assertTrue (!session.isConnected());
|
||||
|
||||
local.close();
|
||||
assertTrue (!local.isConnected());
|
||||
}
|
||||
void SQLiteTest::testTransaction()
|
||||
{
|
||||
Session session (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
||||
@ -3545,6 +3643,7 @@ void SQLiteTest::setUp()
|
||||
|
||||
void SQLiteTest::tearDown()
|
||||
{
|
||||
Connector::enableSharedCache(false);
|
||||
}
|
||||
|
||||
|
||||
@ -3638,7 +3737,10 @@ CppUnit::Test* SQLiteTest::suite()
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testCommitCallback);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testRollbackCallback);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testNotifier);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testSessionTransaction);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testSessionTransactionReadCommitted);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testSessionTransactionReadUncommitted);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testSessionTransactionSerializable);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testSessionTransactionRepeatableRead);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testTransaction);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testTransactor);
|
||||
CppUnit_addTest(pSuite, SQLiteTest, testFTS3);
|
||||
|
@ -132,7 +132,10 @@ public:
|
||||
void testRollbackCallback();
|
||||
void testNotifier();
|
||||
|
||||
void testSessionTransaction();
|
||||
void testSessionTransactionReadCommitted();
|
||||
void testSessionTransactionReadUncommitted();
|
||||
void testSessionTransactionSerializable();
|
||||
void testSessionTransactionRepeatableRead();
|
||||
void testTransaction();
|
||||
void testTransactor();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user