// Copyright (c) 2010 Martin Knafve / hMailServer.com. // http://www.hmailserver.com #include "StdAfx.h" #include ".\databaseconnectionmanager.h" #include "DALConnection.h" #include "DALConnectionFactory.h" #include "DatabaseSettings.h" #include "ADORecordset.h" #include "MySQLRecordset.h" #include "PGRecordset.h" #include "SQLCERecordset.h" #include "MySQLInterface.h" #include "SQLCommand.h" #include "Prerequisites/PrerequisiteList.h" #include "SQLScriptRunner.h" #ifdef _DEBUG #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) #define new DEBUG_NEW #endif #ifdef _DEBUG #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) #define new DEBUG_NEW #endif namespace HM { DatabaseConnectionManager::DatabaseConnectionManager(void) { } DatabaseConnectionManager::~DatabaseConnectionManager(void) { } bool DatabaseConnectionManager::CreateConnections(String &sErrorMessage) { int iNoOfConnectionAttempts = IniFileSettings::Instance()->GetNumberOfDatabaseConnectionAttempts(); int iDelayBetweenConnectionAttempt = IniFileSettings::Instance()->GetDBConnectionAttemptsDelay() * 1000; bool bConnectionOK = false; for (int iTry = 1; iTry <= iNoOfConnectionAttempts ; iTry++) { DALConnection::ConnectionResult iResult = Connect_(sErrorMessage); switch (iResult) { case DALConnection::Connected: // Reset the error message. If we failed to connect the first time, // but succeeded the second, it's not really an error to care about. sErrorMessage = ""; return true; case DALConnection::FatalError: // Skip out and report error. return false; case DALConnection::TemporaryFailure: // We failed to connect to the database server. // Pause a few seconds and then try again. Sleep(iDelayBetweenConnectionAttempt); break; } } return false; } DALConnection::ConnectionResult DatabaseConnectionManager::Connect_(String &sErrorMessage) { int iNoOfConnections = IniFileSettings::Instance()->GetNumberOfDatabaseConnections(); IniFileSettings *pIniFileSettings = IniFileSettings::Instance(); String sProvider = pIniFileSettings->GetDatabaseProvider(); String sServer = pIniFileSettings->GetDatabaseServer(); String sUsername = pIniFileSettings->GetUsername(); String sPassword = pIniFileSettings->GetPassword(); String sDatabase = pIniFileSettings->GetDatabaseName(); String sDatabaseDirectory = pIniFileSettings->GetDatabaseDirectory(); String sDatabaseServerFailoverPartner = pIniFileSettings->GetDatabaseServerFailoverPartner(); long lDBPort = pIniFileSettings->GetDatabasePort(); HM::DatabaseSettings::SQLDBType dbType = IniFileSettings::Instance()->GetDatabaseType(); std::shared_ptr pSettings = std::shared_ptr (new DatabaseSettings(sProvider, sServer, sDatabase, sUsername, sPassword, sDatabaseDirectory, sDatabaseServerFailoverPartner, dbType, lDBPort)); for (int i = 0; i < iNoOfConnections; i++) { std::shared_ptr pConnection = DALConnectionFactory::CreateConnection(pSettings); DALConnection::ConnectionResult result = pConnection->Connect(sErrorMessage); if (result != DALConnection::Connected) return result; available_connections_.insert(pConnection); } // Fetch first connection auto iter = available_connections_.begin(); if (!(*iter)->CheckServerVersion(sErrorMessage)) return DALConnection::FatalError; (*iter)->OnConnected(); return DALConnection::Connected; } void DatabaseConnectionManager::Disconnect() { boost::lock_guard guard(mutex_); //auto iterConnection = available_connections_.begin(); for(std::shared_ptr pConnection : available_connections_) { pConnection->Disconnect(); } available_connections_.clear(); for(std::shared_ptr pConnection : busy_connections_) { pConnection->Disconnect(); } busy_connections_.clear(); } bool DatabaseConnectionManager::Execute(const SQLStatement &statement, __int64 *iInsertID, int iIgnoreErrors, String &sErrorMessage) { return Execute(statement.GetCommand(), iInsertID, iIgnoreErrors, sErrorMessage); } bool DatabaseConnectionManager::Execute(const SQLCommand &command, __int64 *iInsertID, int iIgnoreErrors, String &sErrorMessage) { std::shared_ptr pDALConn = GetConnection_(); if (!pDALConn) { assert(0); return false; } bool bResult = pDALConn->Execute(command, sErrorMessage, iInsertID, iIgnoreErrors); ReleaseConnection_(pDALConn); return bResult; } std::shared_ptr DatabaseConnectionManager::OpenRecordset(const SQLStatement &statement) { return OpenRecordset(statement.GetCommand()); } std::shared_ptr DatabaseConnectionManager::OpenRecordset(const SQLCommand &command) { std::shared_ptr pRecordset; std::shared_ptr pDALConn = GetConnection_(); if (!pDALConn) { assert(0); return pRecordset; } pRecordset = pDALConn->CreateRecordset(); if (!pRecordset->Open(pDALConn, command)) pRecordset.reset(); ReleaseConnection_(pDALConn); return pRecordset; } void DatabaseConnectionManager::ReleaseConnection_(std::shared_ptr pConnection) { boost::lock_guard guard(mutex_); auto iterConnection = busy_connections_.find(pConnection); if (iterConnection == busy_connections_.end()) { assert(0); return; } busy_connections_.erase(iterConnection); // Locate an available connection available_connections_.insert(pConnection); } int DatabaseConnectionManager::GetCurrentDatabaseVersion() { SQLCommand command("select * from hm_dbversion"); std::shared_ptr pRS = OpenRecordset(command); if (!pRS) return 0; int iRetVal = pRS->GetLongValue("value"); return iRetVal; } std::shared_ptr DatabaseConnectionManager::GetConnection_() { // Loop until we find a free connection while (1) { // We want to sleep outside of the lock-scope. Hence the inner scope here. { boost::lock_guard guard(mutex_); // Locate an available connection auto iterConnection = available_connections_.begin(); if (iterConnection != available_connections_.end()) { // Remove the connection from free and add to busy std::shared_ptr pConn = (*iterConnection); // Remove it from the list of available connections available_connections_.erase(iterConnection); busy_connections_.insert(pConn); return pConn; } else { if (busy_connections_.size() == 0 && available_connections_.size() == 0) { // There's no available connections at all. Nothing to wait for. std::shared_ptr pEmpty; return pEmpty; } } } Sleep(10); } } std::shared_ptr DatabaseConnectionManager::BeginTransaction(String &sErrorMessage) { std::shared_ptr pDALConnection = GetConnection_(); if (!pDALConnection->BeginTransaction(sErrorMessage)) { // Could not start database transaction. std::shared_ptr pEmpty; return pEmpty; } return pDALConnection; } bool DatabaseConnectionManager::CommitTransaction(std::shared_ptr pConnection, String &sErrorMessage) { bool bResult = pConnection->CommitTransaction(sErrorMessage); ReleaseConnection_(pConnection); return bResult; } bool DatabaseConnectionManager::RollbackTransaction(std::shared_ptr pConnection, String &sErrorMessage) { bool bResult = pConnection->RollbackTransaction(sErrorMessage); ReleaseConnection_(pConnection); return bResult; } bool DatabaseConnectionManager::GetIsConnected() { boost::lock_guard guard(mutex_); size_t iNoOfConnections = busy_connections_.size() + available_connections_.size(); if (iNoOfConnections == 0) return false; return true; } bool DatabaseConnectionManager::ExecuteScript(const String &sFile, String &sErrorMessage) { std::shared_ptr pConnection = GetConnection_(); SQLScriptRunner scriptRunner; bool result = scriptRunner.ExecuteScript(pConnection, sFile, sErrorMessage); ReleaseConnection_(pConnection); return result; } bool DatabaseConnectionManager::EnsuresPrerequisites(long DBVersion, String &sErrorMessage) { PrerequisiteList prerequisites; std::shared_ptr pConnection = GetConnection_(); bool result = prerequisites.Ensure(pConnection, DBVersion, sErrorMessage); ReleaseConnection_(pConnection); return result; } }