mxoemu/Reality/Source/Database/Database.cpp

524 lines
12 KiB
C++
Raw Permalink Normal View History

// ***************************************************************************
//
// Reality - The Matrix Online Server Emulator
2009-08-11 12:17:38 +00:00
// Copyright (C) 2006-2010 Rajko Stojadinovic
// http://mxoemu.info
2009-08-11 12:17:38 +00:00
//
// ---------------------------------------------------------------------------
2009-08-11 12:17:38 +00:00
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
2009-08-11 12:17:38 +00:00
//
// 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 Affero General Public License for more details.
2009-08-11 12:17:38 +00:00
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2009-08-11 12:17:38 +00:00
//
// ---------------------------------------------------------------------------
//
// ***************************************************************************
2009-08-11 12:17:38 +00:00
#include "DatabaseEnv.h"
#include "../Log.h"
#include "../Util.h"
#include "../Threading/Threading.h"
SQLCallbackBase::~SQLCallbackBase()
{
}
Database::Database() : ThreadContext()
{
_counter=0;
ThreadRunning = true;
}
Database::~Database()
{
2010-02-17 07:17:06 +00:00
Shutdown();
2009-08-11 12:17:38 +00:00
}
bool Database::Initialize(const char* Hostname, unsigned int port, const char* Username, const char* Password, const char* DatabaseName, uint32 ConnectionCount, uint32 BufferSize)
{
uint32 i;
MYSQL * temp, * temp2;
my_bool my_true = true;
mHostname = string(Hostname);
mUsername = string(Username);
mPassword = string(Password);
mDatabaseName = string(DatabaseName);
2010-02-13 14:32:04 +00:00
INFO_LOG(format("MySQLDatabase Connecting to `%1%`, database `%2%`...") % Hostname % DatabaseName);
2009-08-11 12:17:38 +00:00
for( i = 0; i < ConnectionCount; ++i )
{
temp = mysql_init( NULL );
if(mysql_options(temp, MYSQL_SET_CHARSET_NAME, "utf8"))
WARNING_LOG("MySQLDatabase Could not set utf8 character set.");
if (mysql_options(temp, MYSQL_OPT_RECONNECT, &my_true))
WARNING_LOG("MYSQL_OPT_RECONNECT could not be set, connection drops may occur but will be counteracted.");
temp2 = mysql_real_connect( temp, Hostname, Username, Password, DatabaseName, port, NULL, 0 );
if( temp2 == NULL )
{
2010-02-13 14:32:04 +00:00
CRITICAL_LOG(format("MySQLDatabase Connection failed due to: `%1%`") % mysql_error( temp ) );
2009-08-11 12:17:38 +00:00
return false;
}
2010-02-17 07:17:06 +00:00
m_connections.push_back(DatabaseConnection(temp2));
2009-08-11 12:17:38 +00:00
}
// Spawn Database thread
ThreadPool.ExecuteTask(this);
// launch the query thread
qt = new QueryThread(this);
ThreadPool.ExecuteTask(qt);
return true;
}
2010-02-17 07:17:06 +00:00
DatabaseConnection &Database::GetFreeConnection()
2009-08-11 12:17:38 +00:00
{
uint32 i = 0;
for(;;)
{
2010-02-17 07:17:06 +00:00
DatabaseConnection &con = m_connections[ ((i++) % m_connections.size()) ];
if(con.Busy.AttemptAcquire())
2009-08-11 12:17:38 +00:00
return con;
// sleep every 20 iterations, otherwise this can cause 100% cpu if the db link goes dead
if( !(i % 20) )
{
Sleep(10);
}
}
// shouldn't be reached
2010-02-17 07:17:06 +00:00
throw(exception());
2009-08-11 12:17:38 +00:00
}
2010-02-13 14:32:04 +00:00
QueryResult *Database::Query( string QueryString )
2009-08-11 12:17:38 +00:00
{
// Send the query
QueryResult * qResult = NULL;
2010-02-17 07:17:06 +00:00
DatabaseConnection &con = GetFreeConnection();
2009-08-11 12:17:38 +00:00
2010-02-13 14:32:04 +00:00
if( _SendQuery( con, QueryString.c_str(), false ) )
2009-08-11 12:17:38 +00:00
qResult = _StoreQueryResult( con );
2010-02-17 07:17:06 +00:00
con.Busy.Release();
2009-08-11 12:17:38 +00:00
return qResult;
}
2010-02-17 07:17:06 +00:00
QueryResult * Database::FQuery( string QueryString, DatabaseConnection &con)
2009-08-11 12:17:38 +00:00
{
// Send the query
QueryResult * qResult = NULL;
2010-02-13 14:32:04 +00:00
if( _SendQuery( con, QueryString.c_str(), false ) )
2009-08-11 12:17:38 +00:00
qResult = _StoreQueryResult( con );
return qResult;
}
2010-02-17 07:17:06 +00:00
void Database::FWaitExecute( string QueryString, DatabaseConnection &con)
2009-08-11 12:17:38 +00:00
{
// Send the query
2010-02-13 14:32:04 +00:00
_SendQuery( con, QueryString.c_str(), false );
2009-08-11 12:17:38 +00:00
}
2010-02-13 14:32:04 +00:00
void QueryBuffer::AddQuery(string fmt)
2009-08-11 12:17:38 +00:00
{
2010-02-16 12:08:42 +00:00
queries.push_back(fmt);
2009-08-11 12:17:38 +00:00
}
2010-02-17 07:17:06 +00:00
void Database::PerformQueryBuffer(QueryBuffer * b, DatabaseConnection &con)
{
if(!b->queries.size())
return;
while (b->queries.size() > 0)
{
string &currQuery = b->queries.front();
_SendQuery(con, currQuery.c_str(), false);
b->queries.pop_front();
}
}
void Database::PerformQueryBuffer(QueryBuffer * b)
2009-08-11 12:17:38 +00:00
{
if(!b->queries.size())
return;
2010-02-17 07:17:06 +00:00
DatabaseConnection &con = GetFreeConnection();
2009-08-11 12:17:38 +00:00
2010-02-16 12:08:42 +00:00
while (b->queries.size() > 0)
2009-08-11 12:17:38 +00:00
{
2010-02-16 12:08:42 +00:00
string &currQuery = b->queries.front();
_SendQuery(con, currQuery.c_str(), false);
b->queries.pop_front();
2009-08-11 12:17:38 +00:00
}
2010-02-17 07:17:06 +00:00
con.Busy.Release();
2009-08-11 12:17:38 +00:00
}
2010-02-17 07:17:06 +00:00
2010-02-13 14:32:04 +00:00
bool Database::Execute( string QueryString)
2009-08-11 12:17:38 +00:00
{
if(!ThreadRunning)
2010-02-13 14:32:04 +00:00
return WaitExecute(QueryString);
2009-08-11 12:17:38 +00:00
2010-02-16 12:08:42 +00:00
queries_queue.push(new string(QueryString));
2009-08-11 12:17:38 +00:00
return true;
}
//this will wait for completion
2010-02-13 14:32:04 +00:00
bool Database::WaitExecute( string QueryString)
2009-08-11 12:17:38 +00:00
{
2010-02-17 07:17:06 +00:00
DatabaseConnection &con = GetFreeConnection();
2010-02-13 14:32:04 +00:00
bool Result = _SendQuery(con, QueryString.c_str(), false);
2010-02-17 07:17:06 +00:00
con.Busy.Release();
2009-08-11 12:17:38 +00:00
return Result;
}
bool Database::run()
{
SetThreadName("Database Executor");
ThreadRunning = true;
2010-02-16 12:08:42 +00:00
string *query = queries_queue.pop();
2010-02-17 07:17:06 +00:00
DatabaseConnection &con = GetFreeConnection();
2009-08-11 12:17:38 +00:00
while(query)
{
2010-02-16 12:08:42 +00:00
_SendQuery( con, query->c_str(), false );
delete query;
2009-08-11 12:17:38 +00:00
if(!m_threadRunning)
break;
query = queries_queue.pop();
}
2010-02-17 07:17:06 +00:00
con.Busy.Release();
2009-08-11 12:17:38 +00:00
if(queries_queue.get_size() > 0)
{
// execute all the remaining queries
query = queries_queue.pop_nowait();
while(query)
{
2010-02-17 07:17:06 +00:00
DatabaseConnection &con = GetFreeConnection();
2010-02-16 12:08:42 +00:00
_SendQuery( con, query->c_str(), false );
2010-02-17 07:17:06 +00:00
con.Busy.Release();
2010-02-16 12:08:42 +00:00
delete query;
2009-08-11 12:17:38 +00:00
query=queries_queue.pop_nowait();
}
}
ThreadRunning = false;
return false;
}
2010-02-13 14:32:04 +00:00
void AsyncQuery::AddQuery( string fmt )
2009-08-11 12:17:38 +00:00
{
AsyncQueryResult res;
res.query = NULL;
res.result = NULL;
2010-08-10 19:11:15 +02:00
size_t len = fmt.length();
2010-02-13 14:32:04 +00:00
if(len>0)
2009-08-11 12:17:38 +00:00
{
res.query = new char[len+1];
res.query[len] = 0;
2010-02-13 14:32:04 +00:00
memcpy(res.query, fmt.c_str(), len);
2009-08-11 12:17:38 +00:00
queries.push_back(res);
}
}
void AsyncQuery::Perform()
{
2010-02-17 07:17:06 +00:00
DatabaseConnection &conn = db->GetFreeConnection();
2009-08-11 12:17:38 +00:00
for(vector<AsyncQueryResult>::iterator itr = queries.begin(); itr != queries.end(); ++itr)
itr->result = db->FQuery(itr->query, conn);
2010-02-17 07:17:06 +00:00
conn.Busy.Release();
2009-08-11 12:17:38 +00:00
func->run(queries);
delete this;
}
AsyncQuery::~AsyncQuery()
{
delete func;
for(vector<AsyncQueryResult>::iterator itr = queries.begin(); itr != queries.end(); ++itr)
{
if(itr->result)
delete itr->result;
delete[] itr->query;
}
queries.clear();
}
void Database::EndThreads()
{
Terminate();
while(ThreadRunning || qt)
{
if(query_buffer.get_size() == 0)
query_buffer.GetCond().Broadcast();
if(queries_queue.get_size() == 0)
queries_queue.GetCond().Broadcast();
Sleep(100);
if(!ThreadRunning)
break;
Sleep(1000);
}
}
bool QueryThread::run( )
{
db->thread_proc_query( );
return true;
}
QueryThread::~QueryThread()
{
db->qt = NULL;
}
void Database::thread_proc_query()
{
QueryBuffer * q;
2010-02-17 07:17:06 +00:00
DatabaseConnection &con = GetFreeConnection();
2009-08-11 12:17:38 +00:00
q = query_buffer.pop( );
while( q != NULL )
{
PerformQueryBuffer( q, con );
delete q;
if( !m_threadRunning )
break;
q = query_buffer.pop( );
}
2010-02-17 07:17:06 +00:00
con.Busy.Release();
2009-08-11 12:17:38 +00:00
// kill any queries
q = query_buffer.pop_nowait( );
while( q != NULL )
{
2010-02-17 07:17:06 +00:00
PerformQueryBuffer( q );
2009-08-11 12:17:38 +00:00
delete q;
q = query_buffer.pop_nowait( );
}
}
void Database::QueueAsyncQuery(AsyncQuery * query)
{
query->db = this;
/*if(qt == NULL)
{
query->Perform();
return;
}
qqueries_queue.push(query);*/
query->Perform();
}
void Database::AddQueryBuffer(QueryBuffer * b)
{
if( qt != NULL )
query_buffer.push( b );
else
{
2010-02-17 07:17:06 +00:00
PerformQueryBuffer( b );
2009-08-11 12:17:38 +00:00
delete b;
}
}
void Database::FreeQueryResult(QueryResult * p)
{
delete p;
}
string Database::EscapeString(std::string Escape)
{
char a2[16384] = {0};
2010-02-17 07:17:06 +00:00
DatabaseConnection &con = GetFreeConnection();
2009-08-11 12:17:38 +00:00
const char * ret;
2010-02-17 07:17:06 +00:00
if(mysql_real_escape_string(con.conn, a2, Escape.c_str(), (unsigned long)Escape.length()) == 0)
2009-08-11 12:17:38 +00:00
ret = Escape.c_str();
else
ret = a2;
2010-02-17 07:17:06 +00:00
con.Busy.Release();
2009-08-11 12:17:38 +00:00
return string(ret);
}
void Database::EscapeLongString(const char * str, uint32 len, stringstream& out)
{
char a2[65536*3] = {0};
2010-02-17 07:17:06 +00:00
DatabaseConnection &con = GetFreeConnection();
2009-08-11 12:17:38 +00:00
const char * ret;
2010-02-17 07:17:06 +00:00
if(mysql_real_escape_string(con.conn, a2, str, (unsigned long)len) == 0)
2009-08-11 12:17:38 +00:00
ret = str;
else
ret = a2;
out.write(a2, (std::streamsize)strlen(a2));
2010-02-17 07:17:06 +00:00
con.Busy.Release();
2009-08-11 12:17:38 +00:00
}
string Database::EscapeString(const char * esc, DatabaseConnection * con)
{
char a2[16384] = {0};
const char * ret;
if(mysql_real_escape_string(con->conn, a2, (char*)esc, (unsigned long)strlen(esc)) == 0)
ret = esc;
else
ret = a2;
return string(ret);
}
2010-02-17 07:17:06 +00:00
bool Database::_SendQuery(DatabaseConnection &con, const char* Sql, bool Self)
2009-08-11 12:17:38 +00:00
{
//dunno what it does ...leaving untouched
2010-02-17 07:17:06 +00:00
int result = mysql_query(con.conn, Sql);
2009-08-11 12:17:38 +00:00
if(result > 0)
{
2010-02-17 07:17:06 +00:00
if( Self == false && _HandleError(con, mysql_errno( con.conn ) ) )
2009-08-11 12:17:38 +00:00
{
// Re-send the query, the connection was successful.
// The true on the end will prevent an endless loop here, as it will
// stop after sending the query twice.
result = _SendQuery(con, Sql, true);
}
else
2010-02-17 07:17:06 +00:00
ERROR_LOG(format("Sql query failed due to [%1%], Query: [%2%]\n") % mysql_error( con.conn ) % Sql);
2009-08-11 12:17:38 +00:00
}
return (result == 0 ? true : false);
}
2010-02-17 07:17:06 +00:00
bool Database::_HandleError(DatabaseConnection &con, uint32 ErrorNumber)
2009-08-11 12:17:38 +00:00
{
// Handle errors that should cause a reconnect to the Database.
switch(ErrorNumber)
{
case 2006: // Mysql server has gone away
case 2008: // Client ran out of memory
case 2013: // Lost connection to sql server during query
case 2055: // Lost connection to sql server - system error
{
// Let's instruct a reconnect to the db when we encounter these errors.
return _Reconnect( con );
}break;
}
return false;
}
QueryResult::QueryResult(MYSQL_RES *res, uint32 fields, uint32 rows) : mResult(res), mFieldCount(fields), mRowCount(rows)
{
mCurrentRow = new Field[fields];
}
QueryResult::~QueryResult()
{
mysql_free_result(mResult);
delete [] mCurrentRow;
}
bool QueryResult::NextRow()
{
MYSQL_ROW row = mysql_fetch_row(mResult);
if(row == NULL)
return false;
for(uint32 i = 0; i < mFieldCount; ++i)
mCurrentRow[i].SetValue(row[i]);
return true;
}
2010-02-17 07:17:06 +00:00
QueryResult * Database::_StoreQueryResult(DatabaseConnection &con)
2009-08-11 12:17:38 +00:00
{
QueryResult *res;
2010-02-17 07:17:06 +00:00
MYSQL_RES * pRes = mysql_store_result( con.conn );
uint32 uRows = (uint32)mysql_affected_rows( con.conn );
uint32 uFields = (uint32)mysql_field_count( con.conn );
2009-08-11 12:17:38 +00:00
if( uRows == 0 || uFields == 0 || pRes == 0 )
{
if( pRes != NULL )
mysql_free_result( pRes );
return NULL;
}
res = new QueryResult( pRes, uFields, uRows );
res->NextRow();
return res;
}
2010-02-17 07:17:06 +00:00
bool Database::_Reconnect(DatabaseConnection &conn)
2009-08-11 12:17:38 +00:00
{
MYSQL * temp, *temp2;
temp = mysql_init( NULL );
temp2 = mysql_real_connect( temp, mHostname.c_str(), mUsername.c_str(), mPassword.c_str(), mDatabaseName.c_str(), mPort, NULL , 0 );
if( temp2 == NULL )
{
2010-02-13 14:32:04 +00:00
CRITICAL_LOG(format("Could not reconnect to database because of `%1%`") % mysql_error( temp ) );
2009-08-11 12:17:38 +00:00
mysql_close( temp );
return false;
}
2010-02-17 07:17:06 +00:00
if( conn.conn != NULL )
mysql_close( conn.conn );
2009-08-11 12:17:38 +00:00
2010-02-17 07:17:06 +00:00
conn.conn = temp;
2009-08-11 12:17:38 +00:00
return true;
}
void Database::CleanupLibs()
{
mysql_library_end();
}
Database *Database::Create()
{
return new Database();
}
void Database::Shutdown()
{
2010-02-17 07:17:06 +00:00
for(connectionsList::iterator it=m_connections.begin();it!=m_connections.end();++it)
2009-08-11 12:17:38 +00:00
{
2010-02-17 07:17:06 +00:00
if( it->conn != NULL )
2009-08-11 12:17:38 +00:00
{
2010-02-17 07:17:06 +00:00
mysql_close(it->conn);
it->conn = NULL;
2009-08-11 12:17:38 +00:00
}
}
2010-02-17 07:17:06 +00:00
m_connections.clear();
2009-08-11 12:17:38 +00:00
}