1
0
mirror of https://github.com/pocoproject/poco.git synced 2025-04-03 01:54:47 +02:00

Serializable Isolation level for SQLite Databases ()

* fix 
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 ()

---------

Co-authored-by: Alexander B <ale.bychuk@gmail.com>
This commit is contained in:
Aleksandar Fabijanic 2024-10-10 03:41:02 -05:00 committed by GitHub
parent eaabd3ff8d
commit 88c4958032
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 162 additions and 15 deletions
Data/SQLite
include/Poco/Data/SQLite
src
testsuite/src

@ -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();