#include "mysql.h" #include #include #if defined(CONF_SQL) #include #include #include #include #endif #include CLock CMysqlConnection::m_SqlDriverLock; CMysqlConnection::CMysqlConnection( const char *pDatabase, const char *pPrefix, const char *pUser, const char *pPass, const char *pIp, int Port, bool Setup) : IDbConnection(pPrefix), #if defined(CONF_SQL) m_NewQuery(false), m_Locked(false), #endif m_Port(Port), m_Setup(Setup), m_InUse(false) { str_copy(m_aDatabase, pDatabase, sizeof(m_aDatabase)); str_copy(m_aUser, pUser, sizeof(m_aUser)); str_copy(m_aPass, pPass, sizeof(m_aPass)); str_copy(m_aIp, pIp, sizeof(m_aIp)); #ifndef CONF_SQL dbg_msg("sql", "Adding MySQL server failed due to MySQL support not enabled during compile time"); #endif } CMysqlConnection::~CMysqlConnection() { } void CMysqlConnection::Print(IConsole *pConsole, const char *Mode) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d", Mode, m_aDatabase, GetPrefix(), m_aUser, m_aIp, m_Port); pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } CMysqlConnection *CMysqlConnection::Copy() { return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_Port, m_Setup); } void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) { str_format(aBuf, BufferSize, "UNIX_TIMESTAMP(%s)", pTimestamp); } IDbConnection::Status CMysqlConnection::Connect() { #if defined(CONF_SQL) if(m_InUse.exchange(true)) return Status::IN_USE; m_NewQuery = true; if(m_pConnection != nullptr) { try { // Connect to specific database m_pConnection->setSchema(m_aDatabase); return Status::SUCCESS; } catch(sql::SQLException &e) { dbg_msg("sql", "MySQL Error: %s", e.what()); } catch(const std::exception &ex) { dbg_msg("sql", "MySQL Error: %s", ex.what()); } catch(const std::string &ex) { dbg_msg("sql", "MySQL Error: %s", ex.c_str()); } catch(...) { dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); } dbg_msg("sql", "FAILURE: SQL connection failed, trying to reconnect"); } try { m_pConnection.reset(); m_pPreparedStmt.reset(); m_pResults.reset(); sql::ConnectOptionsMap connection_properties; connection_properties["hostName"] = sql::SQLString(m_aIp); connection_properties["port"] = m_Port; connection_properties["userName"] = sql::SQLString(m_aUser); connection_properties["password"] = sql::SQLString(m_aPass); connection_properties["OPT_CONNECT_TIMEOUT"] = 60; connection_properties["OPT_READ_TIMEOUT"] = 60; connection_properties["OPT_WRITE_TIMEOUT"] = 120; connection_properties["OPT_RECONNECT"] = true; connection_properties["OPT_CHARSET_NAME"] = sql::SQLString("utf8mb4"); connection_properties["OPT_SET_CHARSET_NAME"] = sql::SQLString("utf8mb4"); // Create connection { CScopeLock GlobalLockScope(&m_SqlDriverLock); sql::Driver *pDriver = get_driver_instance(); m_pConnection.reset(pDriver->connect(connection_properties)); } // Create Statement m_pStmt = std::unique_ptr(m_pConnection->createStatement()); // Apparently OPT_CHARSET_NAME and OPT_SET_CHARSET_NAME are not enough m_pStmt->execute("SET CHARACTER SET utf8mb4;"); if(m_Setup) { char aBuf[1024]; // create database str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase); m_pStmt->execute(aBuf); // Connect to specific database m_pConnection->setSchema(m_aDatabase); FormatCreateRace(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); FormatCreateTeamrace(aBuf, sizeof(aBuf), "VARBINARY(16)"); m_pStmt->execute(aBuf); FormatCreateMaps(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); FormatCreateSaves(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); FormatCreatePoints(aBuf, sizeof(aBuf)); m_pStmt->execute(aBuf); m_Setup = false; } else { // Connect to specific database m_pConnection->setSchema(m_aDatabase); } dbg_msg("sql", "sql connection established"); return Status::SUCCESS; } catch(sql::SQLException &e) { dbg_msg("sql", "MySQL Error: %s", e.what()); } catch(const std::exception &ex) { dbg_msg("sql", "MySQL Error: %s", ex.what()); } catch(const std::string &ex) { dbg_msg("sql", "MySQL Error: %s", ex.c_str()); } catch(...) { dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); } m_InUse.store(false); #endif dbg_msg("sql", "FAILURE: sql connection failed"); return Status::FAILURE; } void CMysqlConnection::Disconnect() { m_InUse.store(false); } void CMysqlConnection::Lock(const char *pTable) { #if defined(CONF_SQL) char aBuf[512]; str_format(aBuf, sizeof(aBuf), "LOCK TABLES %s;", pTable); m_pStmt->execute(aBuf); m_Locked = true; #endif } void CMysqlConnection::Unlock() { #if defined(CONF_SQL) if(m_Locked) { m_pStmt->execute("UNLOCK TABLES;"); m_Locked = false; } #endif } void CMysqlConnection::PrepareStatement(const char *pStmt) { #if defined(CONF_SQL) m_pPreparedStmt.reset(m_pConnection->prepareStatement(pStmt)); m_NewQuery = true; #endif } void CMysqlConnection::BindString(int Idx, const char *pString) { #if defined(CONF_SQL) m_pPreparedStmt->setString(Idx, pString); m_NewQuery = true; #endif } void CMysqlConnection::BindBlob(int Idx, unsigned char *pBlob, int Size) { #if defined(CONF_SQL) // copy blob into string auto Blob = std::string(pBlob, pBlob + Size); m_pPreparedStmt->setString(Idx, Blob); m_NewQuery = true; #endif } void CMysqlConnection::BindInt(int Idx, int Value) { #if defined(CONF_SQL) m_pPreparedStmt->setInt(Idx, Value); m_NewQuery = true; #endif } void CMysqlConnection::BindFloat(int Idx, float Value) { #if defined(CONF_SQL) m_pPreparedStmt->setDouble(Idx, (double)Value); m_NewQuery = true; #endif } bool CMysqlConnection::Step() { #if defined(CONF_SQL) if(m_NewQuery) { m_NewQuery = false; m_pResults.reset(m_pPreparedStmt->executeQuery()); } return m_pResults->next(); #else return false; #endif } bool CMysqlConnection::IsNull(int Col) const { #if defined(CONF_SQL) return m_pResults->isNull(Col); #else return false; #endif } float CMysqlConnection::GetFloat(int Col) const { #if defined(CONF_SQL) return (float)m_pResults->getDouble(Col); #else return 0.0; #endif } int CMysqlConnection::GetInt(int Col) const { #if defined(CONF_SQL) return m_pResults->getInt(Col); #else return 0; #endif } void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize) const { #if defined(CONF_SQL) auto String = m_pResults->getString(Col); str_copy(pBuffer, String.c_str(), BufferSize); #endif } int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const { #if defined(CONF_SQL) auto *Blob = m_pResults->getBlob(Col); Blob->read((char *)pBuffer, BufferSize); int NumRead = Blob->gcount(); delete Blob; return NumRead; #else return 0; #endif } const char *CMysqlConnection::MedianMapTime(char *pBuffer, int BufferSize) const { str_format(pBuffer, BufferSize, "SELECT MEDIAN(Time) " "OVER (PARTITION BY Map) " "FROM %s_race " "GROUP BY Map " "HAVING Map = l.Map", GetPrefix()); return pBuffer; } void CMysqlConnection::AddPoints(const char *pPlayer, int Points) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "INSERT INTO %s_points(Name, Points) " "VALUES (?, ?) " "ON DUPLICATE KEY UPDATE Points=Points+?;", GetPrefix()); PrepareStatement(aBuf); BindString(1, pPlayer); BindInt(2, Points); BindInt(3, Points); Step(); }