mirror of
https://github.com/pocoproject/poco.git
synced 2025-10-24 00:49:46 +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:
committed by
GitHub
parent
eaabd3ff8d
commit
88c4958032
@@ -100,6 +100,12 @@ public:
|
|||||||
|
|
||||||
void setTransactionIsolation(Poco::UInt32 ti);
|
void setTransactionIsolation(Poco::UInt32 ti);
|
||||||
/// Sets the transaction isolation level.
|
/// 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;
|
Poco::UInt32 getTransactionIsolation() const;
|
||||||
/// Returns the transaction isolation level.
|
/// Returns the transaction isolation level.
|
||||||
@@ -144,9 +150,12 @@ private:
|
|||||||
Poco::Mutex _mutex;
|
Poco::Mutex _mutex;
|
||||||
static const std::string DEFERRED_BEGIN_TRANSACTION;
|
static const std::string DEFERRED_BEGIN_TRANSACTION;
|
||||||
static const std::string EXCLUSIVE_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 COMMIT_TRANSACTION;
|
||||||
static const std::string ABORT_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::IMMEDIATE_BEGIN_TRANSACTION("BEGIN IMMEDIATE");
|
||||||
const std::string SessionImpl::COMMIT_TRANSACTION("COMMIT");
|
const std::string SessionImpl::COMMIT_TRANSACTION("COMMIT");
|
||||||
const std::string SessionImpl::ABORT_TRANSACTION("ROLLBACK");
|
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):
|
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),
|
_pDB(0),
|
||||||
_connected(false),
|
_connected(false),
|
||||||
_isTransaction(false),
|
_isTransaction(false),
|
||||||
_transactionType(TransactionType::DEFERRED)
|
_transactionType(TransactionType::DEFERRED),
|
||||||
|
_transactionIsolationLevel(Session::TRANSACTION_READ_COMMITTED)
|
||||||
{
|
{
|
||||||
open();
|
open();
|
||||||
setConnectionTimeout(loginTimeout);
|
setConnectionTimeout(loginTimeout);
|
||||||
@@ -128,28 +132,57 @@ void SessionImpl::rollback()
|
|||||||
|
|
||||||
void SessionImpl::setTransactionIsolation(Poco::UInt32 ti)
|
void SessionImpl::setTransactionIsolation(Poco::UInt32 ti)
|
||||||
{
|
{
|
||||||
if (ti != Session::TRANSACTION_READ_COMMITTED)
|
Poco::Mutex::ScopedLock l(_mutex);
|
||||||
throw Poco::InvalidArgumentException("setTransactionIsolation()");
|
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
|
Poco::UInt32 SessionImpl::getTransactionIsolation() const
|
||||||
{
|
{
|
||||||
return Session::TRANSACTION_READ_COMMITTED;
|
return _transactionIsolationLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool SessionImpl::hasTransactionIsolation(Poco::UInt32 ti) const
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool SessionImpl::isTransactionIsolation(Poco::UInt32 ti) const
|
bool SessionImpl::isTransactionIsolation(Poco::UInt32 ti) const
|
||||||
{
|
{
|
||||||
if (ti == Session::TRANSACTION_READ_COMMITTED) return true;
|
return getTransactionIsolation() == ti;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ using Poco::Int64;
|
|||||||
using Poco::Data::SQLite::Utility;
|
using Poco::Data::SQLite::Utility;
|
||||||
using Poco::delegate;
|
using Poco::delegate;
|
||||||
using Poco::Stopwatch;
|
using Poco::Stopwatch;
|
||||||
|
using Poco::Data::SQLite::Connector;
|
||||||
|
|
||||||
|
|
||||||
class Person
|
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");
|
Session session (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
||||||
assertTrue (session.isConnected());
|
assertTrue (session.isConnected());
|
||||||
@@ -3222,10 +3223,6 @@ void SQLiteTest::testSessionTransaction()
|
|||||||
session << "SELECT count(*) FROM Person", into(count), now;
|
session << "SELECT count(*) FROM Person", into(count), now;
|
||||||
assertTrue (2 == count);
|
assertTrue (2 == count);
|
||||||
|
|
||||||
/* TODO: see http://www.sqlite.org/pragma.html#pragma_read_uncommitted
|
|
||||||
setTransactionIsolation(session, Session::TRANSACTION_READ_UNCOMMITTED);
|
|
||||||
*/
|
|
||||||
|
|
||||||
session.close();
|
session.close();
|
||||||
assertTrue (!session.isConnected());
|
assertTrue (!session.isConnected());
|
||||||
|
|
||||||
@@ -3233,7 +3230,108 @@ void SQLiteTest::testSessionTransaction()
|
|||||||
assertTrue (!local.isConnected());
|
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()
|
void SQLiteTest::testTransaction()
|
||||||
{
|
{
|
||||||
Session session (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
Session session (Poco::Data::SQLite::Connector::KEY, "dummy.db");
|
||||||
@@ -3545,6 +3643,7 @@ void SQLiteTest::setUp()
|
|||||||
|
|
||||||
void SQLiteTest::tearDown()
|
void SQLiteTest::tearDown()
|
||||||
{
|
{
|
||||||
|
Connector::enableSharedCache(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -3638,7 +3737,10 @@ CppUnit::Test* SQLiteTest::suite()
|
|||||||
CppUnit_addTest(pSuite, SQLiteTest, testCommitCallback);
|
CppUnit_addTest(pSuite, SQLiteTest, testCommitCallback);
|
||||||
CppUnit_addTest(pSuite, SQLiteTest, testRollbackCallback);
|
CppUnit_addTest(pSuite, SQLiteTest, testRollbackCallback);
|
||||||
CppUnit_addTest(pSuite, SQLiteTest, testNotifier);
|
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, testTransaction);
|
||||||
CppUnit_addTest(pSuite, SQLiteTest, testTransactor);
|
CppUnit_addTest(pSuite, SQLiteTest, testTransactor);
|
||||||
CppUnit_addTest(pSuite, SQLiteTest, testFTS3);
|
CppUnit_addTest(pSuite, SQLiteTest, testFTS3);
|
||||||
|
|||||||
@@ -132,7 +132,10 @@ public:
|
|||||||
void testRollbackCallback();
|
void testRollbackCallback();
|
||||||
void testNotifier();
|
void testNotifier();
|
||||||
|
|
||||||
void testSessionTransaction();
|
void testSessionTransactionReadCommitted();
|
||||||
|
void testSessionTransactionReadUncommitted();
|
||||||
|
void testSessionTransactionSerializable();
|
||||||
|
void testSessionTransactionRepeatableRead();
|
||||||
void testTransaction();
|
void testTransaction();
|
||||||
void testTransactor();
|
void testTransactor();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user