////////////////////////////////////////////////////////////////////////
// OpenTibia - an opensource roleplaying game
////////////////////////////////////////////////////////////////////////
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
////////////////////////////////////////////////////////////////////////
#include "otpch.h"
#include "scheduler.h"
#include "database.h"
#include "databasemysql.h"
#ifdef __MYSQL_ALT_INCLUDE__
#include "errmsg.h"
#else
#include
#endif
#include
#include "configmanager.h"
extern ConfigManager g_config;
DatabaseMySQL::DatabaseMySQL()
{
m_connected = false;
if(!mysql_init(&m_handle))
{
std::cout << std::endl << "Failed to initialize MySQL connection handler." << std::endl;
return;
}
uint32_t readTimeout = g_config.getNumber(ConfigManager::MYSQL_READ_TIMEOUT);
if(readTimeout)
mysql_options(&m_handle, MYSQL_OPT_READ_TIMEOUT, (const char*)&readTimeout);
uint32_t writeTimeout = g_config.getNumber(ConfigManager::MYSQL_WRITE_TIMEOUT);
if(writeTimeout)
mysql_options(&m_handle, MYSQL_OPT_WRITE_TIMEOUT, (const char*)&writeTimeout);
connect();
if(mysql_get_client_version() <= 50019)
{
//MySQL servers <= 5.0.19 has a bug where MYSQL_OPT_RECONNECT is (incorrectly) reset by mysql_real_connect calls
//See http://dev.mysql.com/doc/refman/5.0/en/mysql-options.html for more information.
std::cout << std::endl << "> WARNING: Outdated MySQL server detected, consider upgrading to a newer version." << std::endl;
}
if(g_config.getBool(ConfigManager::HOUSE_STORAGE))
{
//we cannot lock mutex here :)
if(DBResult* result = storeQuery("SHOW variables LIKE 'max_allowed_packet';"))
{
if(result->getDataLong("Value") < 16776192)
{
std::cout << std::endl << "> WARNING: max_allowed_packet might be set too low for binary map storage." << std::endl;
std::cout << "Use the following query to raise max_allow_packet: SET GLOBAL max_allowed_packet = 16776192;" << std::endl;
}
result->free();
}
}
int32_t keepAlive = g_config.getNumber(ConfigManager::SQL_KEEPALIVE);
if(keepAlive)
Scheduler::getScheduler().addEvent(createSchedulerTask((keepAlive * 1000), boost::bind(&DatabaseMySQL::keepAlive, this)));
}
bool DatabaseMySQL::getParam(DBParam_t param)
{
switch(param)
{
case DBPARAM_MULTIINSERT:
return true;
default:
break;
}
return false;
}
bool DatabaseMySQL::rollback()
{
if(!m_connected)
return false;
if(mysql_rollback(&m_handle))
{
std::cout << "mysql_rollback() - MYSQL ERROR: " << mysql_error(&m_handle) << " (" << mysql_errno(&m_handle) << ")" << std::endl;
return false;
}
return true;
}
bool DatabaseMySQL::commit()
{
if(!m_connected)
return false;
if(mysql_commit(&m_handle))
{
std::cout << "mysql_commit() - MYSQL ERROR: " << mysql_error(&m_handle) << " (" << mysql_errno(&m_handle) << ")" << std::endl;
return false;
}
return true;
}
bool DatabaseMySQL::executeQuery(const std::string &query)
{
if(!m_connected)
return false;
bool state = true;
if(mysql_real_query(&m_handle, query.c_str(), query.length()))
{
int32_t error = mysql_errno(&m_handle);
if((error == CR_UNKNOWN_ERROR || error == CR_SERVER_LOST || error == CR_SERVER_GONE_ERROR) && reconnect())
return executeQuery(query);
state = false;
std::cout << "mysql_real_query(): " << query << " - MYSQL ERROR: " << mysql_error(&m_handle) << " (" << error << ")" << std::endl;
}
if(MYSQL_RES* tmp = mysql_store_result(&m_handle))
{
mysql_free_result(tmp);
tmp = NULL;
}
return state;
}
DBResult* DatabaseMySQL::storeQuery(const std::string &query)
{
if(!m_connected)
return NULL;
int32_t error = 0;
if(mysql_real_query(&m_handle, query.c_str(), query.length()))
{
error = mysql_errno(&m_handle);
if((error == CR_UNKNOWN_ERROR || error == CR_SERVER_LOST || error == CR_SERVER_GONE_ERROR) && reconnect())
return storeQuery(query);
std::cout << "mysql_real_query(): " << query << " - MYSQL ERROR: " << mysql_error(&m_handle) << " (" << error << ")" << std::endl;
return NULL;
}
if(MYSQL_RES* tmp = mysql_store_result(&m_handle))
{
DBResult* res = (DBResult*)new MySQLResult(tmp);
return verifyResult(res);
}
error = mysql_errno(&m_handle);
if((error == CR_UNKNOWN_ERROR || error == CR_SERVER_LOST || error == CR_SERVER_GONE_ERROR) && reconnect())
return storeQuery(query);
std::cout << "mysql_store_result(): " << query << " - MYSQL ERROR: " << mysql_error(&m_handle) << " (" << error << ")" << std::endl;
return NULL;
}
std::string DatabaseMySQL::escapeBlob(const char* s, uint32_t length)
{
if(!s)
return "''";
char* output = new char[length * 2 + 1];
mysql_real_escape_string(&m_handle, output, s, length);
std::string res = "'";
res += output;
res += "'";
delete[] output;
return res;
}
void DatabaseMySQL::keepAlive()
{
int32_t delay = g_config.getNumber(ConfigManager::SQL_KEEPALIVE);
if(delay)
{
if(time(NULL) > (m_use + delay) && mysql_ping(&m_handle))
reconnect();
Scheduler::getScheduler().addEvent(createSchedulerTask((delay * 1000), boost::bind(&DatabaseMySQL::keepAlive, this)));
}
}
bool DatabaseMySQL::connect()
{
if(m_connected)
{
m_connected = false;
mysql_close(&m_handle);
OTSYS_SLEEP(1000);
}
if(!mysql_real_connect(&m_handle, g_config.getString(ConfigManager::SQL_HOST).c_str(), g_config.getString(ConfigManager::SQL_USER).c_str(),
g_config.getString(ConfigManager::SQL_PASS).c_str(), g_config.getString(ConfigManager::SQL_DB).c_str(), g_config.getNumber(
ConfigManager::SQL_PORT), NULL, 0))
{
std::cout << "Failed connecting to database - MYSQL ERROR: " << mysql_error(&m_handle) << " (" << mysql_errno(&m_handle) << ")" << std::endl;
return false;
}
m_connected = true;
m_attempts = 0;
return true;
}
bool DatabaseMySQL::reconnect()
{
while(m_attempts <= MAX_RECONNECT_ATTEMPTS)
{
m_attempts++;
if(connect())
return true;
}
std::cout << "Unable to reconnect - too many attempts, limit exceeded!" << std::endl;
return false;
}
int32_t MySQLResult::getDataInt(const std::string &s)
{
listNames_t::iterator it = m_listNames.find(s);
if(it != m_listNames.end())
{
if(m_row[it->second] == NULL)
return 0;
return atoi(m_row[it->second]);
}
if(refetch())
return getDataInt(s);
std::cout << "Error during getDataInt(" << s << ")." << std::endl;
return 0; // Failed
}
int64_t MySQLResult::getDataLong(const std::string &s)
{
listNames_t::iterator it = m_listNames.find(s);
if(it != m_listNames.end())
{
if(m_row[it->second] == NULL)
return 0;
return ATOI64(m_row[it->second]);
}
if(refetch())
return getDataLong(s);
std::cout << "Error during getDataLong(" << s << ")." << std::endl;
return 0; // Failed
}
std::string MySQLResult::getDataString(const std::string &s)
{
listNames_t::iterator it = m_listNames.find(s);
if(it != m_listNames.end())
{
if(m_row[it->second] == NULL)
return "";
return std::string(m_row[it->second]);
}
if(refetch())
return getDataString(s);
std::cout << "Error during getDataString(" << s << ")." << std::endl;
return ""; // Failed
}
const char* MySQLResult::getDataStream(const std::string &s, uint64_t &size)
{
listNames_t::iterator it = m_listNames.find(s);
if(it != m_listNames.end())
{
if(m_row[it->second] == NULL)
{
size = 0;
return NULL;
}
size = mysql_fetch_lengths(m_handle)[it->second];
return m_row[it->second];
}
if(refetch())
return getDataStream(s, size);
std::cout << "Error during getDataStream(" << s << ")." << std::endl;
size = 0;
return NULL; // Failed
}
void MySQLResult::free()
{
if(m_handle)
{
mysql_free_result(m_handle);
m_handle = NULL;
m_listNames.clear();
delete this;
}
else
std::cout << "[Warning - MySQLResult::free] Trying to free already freed result." << std::endl;
}
bool MySQLResult::next()
{
m_row = mysql_fetch_row(m_handle);
return m_row != NULL;
}
void MySQLResult::fetch()
{
m_listNames.clear();
int32_t i = 0;
MYSQL_FIELD* field;
while((field = mysql_fetch_field(m_handle)))
m_listNames[field->name] = i++;
}
bool MySQLResult::refetch()
{
if(m_attempts >= MAX_REFETCH_ATTEMPTS)
return false;
fetch();
m_attempts++;
return true;
}
MySQLResult::MySQLResult(MYSQL_RES* result)
{
m_attempts = 0;
if(result)
{
m_handle = result;
fetch();
}
else
delete this;
}