poco/Data/PostgreSQL/src/SessionHandle.cpp
2022-05-17 07:33:50 +02:00

582 lines
12 KiB
C++

//
// SessionHandle.cpp
//
// Library: Data/PostgreSQL
// Package: PostgreSQL
// Module: SessionHandle
//
// Copyright (c) 2015, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/Data/PostgreSQL/SessionHandle.h"
#include "Poco/Data/PostgreSQL/PostgreSQLException.h"
#include "Poco/Data/PostgreSQL/PostgreSQLTypes.h"
#include "Poco/Data/Session.h"
#include "Poco/NumberFormatter.h"
#define POCO_POSTGRESQL_VERSION_NUMBER ((NDB_VERSION_MAJOR<<16) | (NDB_VERSION_MINOR<<8) | (NDB_VERSION_BUILD&0xFF))
namespace Poco {
namespace Data {
namespace PostgreSQL {
//const std::string SessionHandle::POSTGRESQL_READ_UNCOMMITTED = "READ UNCOMMITTED";
const std::string SessionHandle::POSTGRESQL_READ_COMMITTED = "READ COMMITTED";
const std::string SessionHandle::POSTGRESQL_REPEATABLE_READ = "REPEATABLE READ";
const std::string SessionHandle::POSTGRESQL_SERIALIZABLE = "SERIALIZABLE";
SessionHandle::SessionHandle():
_pConnection(0),
_inTransaction(false),
_isAutoCommit(true),
_isAsynchronousCommit(false),
_tranactionIsolationLevel(Session::TRANSACTION_READ_COMMITTED)
{
}
SessionHandle::~SessionHandle()
{
try
{
disconnect();
}
catch (...)
{
}
}
bool SessionHandle::isConnected() const
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
return isConnectedNoLock();
}
bool SessionHandle::isConnectedNoLock() const
{
// DO NOT ACQUIRE THE MUTEX IN PRIVATE METHODS
if (_pConnection && PQstatus(_pConnection) == CONNECTION_OK)
{
return true;
}
return false;
}
void SessionHandle::connect(const std::string& aConnectionString)
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (isConnectedNoLock())
{
throw ConnectionFailedException("Already Connected");
}
_pConnection = PQconnectdb(aConnectionString.c_str());
if (!isConnectedNoLock())
{
throw ConnectionFailedException(std::string("Connection Error: ") + lastErrorNoLock());
}
_connectionString = aConnectionString;
}
void SessionHandle::connect(const char* aConnectionString)
{
connect(std::string(aConnectionString));
}
void SessionHandle::connect(const char* aHost, const char* aUser, const char* aPassword,
const char* aDatabase, unsigned short aPort, unsigned int aConnectionTimeout)
{
std::string connectionString;
connectionString.append("host=");
connectionString.append(aHost);
connectionString.append(" ");
connectionString.append("user=");
connectionString.append(aUser);
connectionString.append(" ");
connectionString.append("password=");
connectionString.append(aPassword);
connectionString.append(" ");
connectionString.append("dbname=");
connectionString.append(aDatabase);
connectionString.append(" ");
connectionString.append("port=");
Poco::NumberFormatter::append(connectionString, aPort);
connectionString.append(" ");
connectionString.append("connect_timeout=");
Poco::NumberFormatter::append(connectionString, aConnectionTimeout);
connect(connectionString);
}
void SessionHandle::disconnect()
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (isConnectedNoLock())
{
PQfinish(_pConnection);
_pConnection = 0;
_connectionString = std::string();
_inTransaction= false;
_isAutoCommit = true;
_isAsynchronousCommit = false;
_tranactionIsolationLevel = Session::TRANSACTION_READ_COMMITTED;
}
}
// TODO: Figure out what happens if a connection is reset with a pending transaction
bool SessionHandle::reset()
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (_pConnection)
{
PQreset(_pConnection);
}
if (isConnectedNoLock())
{
return true;
}
return false;
}
std::string SessionHandle::lastError() const
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
return std::string();
}
return lastErrorNoLock();
}
std::string SessionHandle::lastErrorNoLock() const
{
// DO NOT ACQUIRE THE MUTEX IN PRIVATE METHODS
std::string lastErrorString (0 != _pConnection ? PQerrorMessage(_pConnection) : "not connected");
return lastErrorString;
}
void SessionHandle::startTransaction()
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
if (_inTransaction)
{
return; // NO-OP
}
PGresult* pPQResult = PQexec(_pConnection, "BEGIN");
PQResultClear resultClearer(pPQResult);
if (PQresultStatus(pPQResult) != PGRES_COMMAND_OK)
{
throw StatementException(std::string("BEGIN statement failed:: ") + lastErrorNoLock());
}
_inTransaction = true;
}
void SessionHandle::commit()
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
PGresult* pPQResult = PQexec(_pConnection, "COMMIT");
PQResultClear resultClearer(pPQResult);
if (PQresultStatus(pPQResult) != PGRES_COMMAND_OK)
{
throw StatementException(std::string("COMMIT statement failed:: ") + lastErrorNoLock());
}
_inTransaction = false;
deallocateStoredPreparedStatements();
}
void SessionHandle::rollback()
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
PGresult* pPQResult = PQexec(_pConnection, "ROLLBACK");
PQResultClear resultClearer(pPQResult);
if (PQresultStatus(pPQResult) != PGRES_COMMAND_OK)
{
throw StatementException(std::string("ROLLBACK statement failed:: ") + lastErrorNoLock());
}
_inTransaction = false;
deallocateStoredPreparedStatements();
}
void SessionHandle::setAutoCommit(bool aShouldAutoCommit)
{
if (aShouldAutoCommit == _isAutoCommit)
{
return;
}
if (aShouldAutoCommit)
{
commit(); // end any in process transaction
}
else
{
startTransaction(); // start a new transaction
}
_isAutoCommit = aShouldAutoCommit;
}
void SessionHandle::setAsynchronousCommit(bool aShouldAsynchronousCommit)
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
if (aShouldAsynchronousCommit == _isAsynchronousCommit)
{
return;
}
PGresult* pPQResult = PQexec(_pConnection, aShouldAsynchronousCommit ? "SET SYNCHRONOUS COMMIT TO OFF" : "SET SYNCHRONOUS COMMIT TO ON");
PQResultClear resultClearer(pPQResult);
if (PQresultStatus(pPQResult) != PGRES_COMMAND_OK)
{
throw StatementException(std::string("SET SYNCHRONUS COMMIT statement failed:: ") + lastErrorNoLock());
}
_isAsynchronousCommit = aShouldAsynchronousCommit;
}
void SessionHandle::cancel()
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
PGcancel* ptrPGCancel = PQgetCancel(_pConnection);
PGCancelFree cancelFreer(ptrPGCancel);
PQcancel(ptrPGCancel, 0, 0); // no error buffer
}
void SessionHandle::setTransactionIsolation(Poco::UInt32 aTI)
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
if (aTI == _tranactionIsolationLevel)
{
return;
}
if (!hasTransactionIsolation(aTI))
{
throw Poco::InvalidArgumentException("setTransactionIsolation()");
}
std::string isolationLevel;
switch (aTI)
{
case Session::TRANSACTION_READ_COMMITTED:
isolationLevel = POSTGRESQL_READ_COMMITTED; break;
case Session::TRANSACTION_REPEATABLE_READ:
isolationLevel = POSTGRESQL_REPEATABLE_READ; break;
case Session::TRANSACTION_SERIALIZABLE:
isolationLevel = POSTGRESQL_SERIALIZABLE; break;
}
PGresult* pPQResult = PQexec(_pConnection, Poco::format("SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL %s", isolationLevel).c_str());
PQResultClear resultClearer(pPQResult);
if (PQresultStatus(pPQResult) != PGRES_COMMAND_OK)
{
throw StatementException(std::string("set transaction isolation statement failed: ") + lastErrorNoLock());
}
_tranactionIsolationLevel = aTI;
}
Poco::UInt32 SessionHandle::transactionIsolation()
{
return _tranactionIsolationLevel;
}
bool SessionHandle::hasTransactionIsolation(Poco::UInt32 aTI)
{
return Session::TRANSACTION_READ_COMMITTED == aTI
|| Session::TRANSACTION_REPEATABLE_READ == aTI
|| Session::TRANSACTION_SERIALIZABLE == aTI;
}
void SessionHandle::deallocatePreparedStatement(const std::string& aPreparedStatementToDeAllocate)
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
if (!_inTransaction)
{
deallocatePreparedStatementNoLock(aPreparedStatementToDeAllocate);
}
else
{
try
{
_preparedStatementsToBeDeallocated.push_back(aPreparedStatementToDeAllocate);
}
catch (std::bad_alloc&)
{
}
}
}
void SessionHandle::deallocatePreparedStatementNoLock(const std::string& aPreparedStatementToDeAllocate)
{
PGresult* pPQResult = PQexec(_pConnection, (std::string("DEALLOCATE ") + aPreparedStatementToDeAllocate).c_str());
PQResultClear resultClearer(pPQResult);
if (PQresultStatus(pPQResult) != PGRES_COMMAND_OK)
{
throw StatementException(std::string("DEALLOCATE statement failed: ") + lastErrorNoLock());
}
}
void SessionHandle::deallocateStoredPreparedStatements()
{
// DO NOT ACQUIRE THE MUTEX IN PRIVATE METHODS
while (!_preparedStatementsToBeDeallocated.empty())
{
deallocatePreparedStatementNoLock(_preparedStatementsToBeDeallocated.back());
_preparedStatementsToBeDeallocated.pop_back();
}
}
int SessionHandle::serverVersion() const
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
return PQserverVersion(_pConnection);
}
int SessionHandle::serverProcessID() const
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
return PQbackendPID(_pConnection);
}
int SessionHandle::protocoVersion() const
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
return PQprotocolVersion(_pConnection);
}
std::string SessionHandle::clientEncoding() const
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
return pg_encoding_to_char(PQclientEncoding(_pConnection));
}
std::string SessionHandle::parameterStatus(const std::string& param) const
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
if (!isConnectedNoLock())
{
throw NotConnectedException();
}
const char* pValue = PQparameterStatus(_pConnection, param.c_str());
if (pValue)
return std::string(pValue);
else
return std::string();
}
int SessionHandle::libpqVersion() const
{
return PQlibVersion();
}
SessionParametersMap SessionHandle::setConnectionInfoParameters(PQconninfoOption* pConnInfOpt)
{
SessionParametersMap sessionParametersMap;
while (pConnInfOpt->keyword)
{
try
{
std::string keyword = pConnInfOpt->keyword ? pConnInfOpt->keyword : std::string();
std::string environmentVariableVersion = pConnInfOpt->envvar ? pConnInfOpt->envvar : std::string();
std::string compiledVersion = pConnInfOpt->compiled ? pConnInfOpt->compiled : std::string();
std::string currentValue = pConnInfOpt->val ? pConnInfOpt->val : std::string();
std::string dialogLabel = pConnInfOpt->label? pConnInfOpt->label: std::string();
std::string dialogDisplayCharacter = pConnInfOpt->dispchar ? pConnInfOpt->dispchar : std::string();
int dialogDisplaysize = pConnInfOpt->dispsize;
SessionParameters connParams(keyword, environmentVariableVersion, compiledVersion,
currentValue, dialogLabel, dialogDisplayCharacter, dialogDisplaysize);
sessionParametersMap.insert(SessionParametersMap::value_type(connParams.keyword(), connParams));
}
catch (std::bad_alloc&)
{
}
++pConnInfOpt;
}
return sessionParametersMap;
}
SessionParametersMap SessionHandle::connectionDefaultParameters()
{
PQconninfoOption* ptrConnInfoOptions = PQconndefaults();
PQConnectionInfoOptionsFree connectionOptionsFreeer(ptrConnInfoOptions);
return setConnectionInfoParameters(ptrConnInfoOptions);
}
SessionParametersMap SessionHandle::connectionParameters() const
{
if (!isConnected())
{
throw NotConnectedException();
}
PQconninfoOption* ptrConnInfoOptions = 0;
{
Poco::FastMutex::ScopedLock mutexLocker(_sessionMutex);
ptrConnInfoOptions = PQconninfo(_pConnection);
}
PQConnectionInfoOptionsFree connectionOptionsFreeer(ptrConnInfoOptions);
return setConnectionInfoParameters(ptrConnInfoOptions);
}
} } } // Poco::Data::PostgreSQL