/*
* Ascent MMORPG Server
* Copyright (C) 2005-2007 Ascent Team
*
* 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
* 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 "../DatabaseEnv.h"
#include "../../CrashHandler.h"
#include "../../NGLog.h"
#ifdef DATABASE_SUPPORT_MYSQL
MySQLDatabase::MySQLDatabase() : Database(DATABASE_TYPE_MYSQL)
{
ThreadType = THREADTYPE_DATABASE;
_counter=0;
Connections = NULL;
mConnectionCount = -1; // Not connected.
// mNextPing = getMSTime();
delete_after_use = false;
ThreadRunning = true;
}
MySQLDatabase::~MySQLDatabase()
{
//Shutdown();
delete [] Connections;
}
bool MySQLDatabase::Initialize(const char* Hostname, unsigned int Port, const char* Username, const char* Password, const char* DatabaseName, uint32 ConnectionCount, uint32 BufferSize)
{
mConnectionCount = ConnectionCount;
mHostname = Hostname;
mUsername = Username;
mPassword = Password;
mDatabaseName = DatabaseName;
mPort = Port;
Connections = new MysqlCon[mConnectionCount];
Log.Notice("Database", "Connecting to MySQL on %s with (%s : *********)", mHostname.c_str(), mUsername.c_str());
for(int i = 0; i < mConnectionCount; ++i)
{
Connections[i].busy = false;
if(!Connect(&Connections[i]))
return false;
}
// Spawn MySQLDatabase thread
launch_thread(this);
return true;
}
bool MySQLDatabase::Connect(MysqlCon * con)
{
MYSQL* Descriptor = mysql_init(NULL);
if(mysql_options(Descriptor, MYSQL_SET_CHARSET_NAME, "utf8"))
sLog.outString("sql: Could not set utf8 character set [!!]");
// MYSQL* Descriptor2 = Descriptor;
// Set reconnect
my_bool my_true = true;
if (mysql_options(Descriptor, MYSQL_OPT_RECONNECT, &my_true))
sLog.outString("sql: MYSQL_OPT_RECONNECT could not be set, connection drops may occur but will be counteracted.");
con->con = mysql_real_connect(Descriptor, mHostname.c_str(),
mUsername.c_str(), mPassword.c_str(), "", mPort, NULL, 0);
if(con->con == NULL)
{
printf("sql: Connection failed. Reason was `%s`", mysql_error(Descriptor));
return false;
}
if(mysql_select_db(con->con, mDatabaseName.c_str()))
{
sLog.outError("sql: Select of MySQLDatabase %s failed due to `%s`", mDatabaseName.c_str(),
mysql_error(con->con));
return false;
}
return true;
}
bool MySQLDatabase::Disconnect(MysqlCon * con)
{
if(con->con)
{
mysql_close(con->con);
con->con = NULL;
return true;
}
return false;
}
void MySQLDatabase::CheckConnections()
{
/*
// Check every 30 seconds (TODO: MAKE CUSTOMIZABLE)
if(getMSTime() < mNextPing)
return;
mNextPing = getMSTime() + 60000;
mSearchMutex.Acquire();
for(uint32 i = 0; i < mNormalCount; ++i)
{
if(InUseMarkers[i].AttemptAcquire())
{
if(Connections[i] && mysql_ping(Connections[i]))
{
sLog.outString("sql: mysql_ping failed, attempting to reconnect to MySQLDatabase...");
Disconnect(i);
if(!Connect(i)) {
sLog.outError("sql: Connection %u was unable to reconnect. This could mean a failure with your MySQLDatabase.");
Disconnect(i);
} else {
SelectDatabase(i);
}
InUseMarkers[i].Release();
} else if(Connections[i] == 0)
{
// Attempt to reconnect.
if(Connect(i) && SelectDatabase(i))
{
sLog.outString("sql: Connection %u re-established.", i);
}
}
InUseMarkers[i].Release();
}
}
mSearchMutex.Release();
*/
}
MysqlCon * MySQLDatabase::GetFreeConnection()
{
lock.Acquire();
while(true)
{
MysqlCon *con=&Connections[(++_counter)%mConnectionCount];
if(!con->busy)
{
con->busy=true;
lock.Release();
return con;
}
}
}
string MySQLDatabase::EscapeString(string Escape)
{
char a2[16384] = {0};
MysqlCon * con = GetFreeConnection();
const char * ret;
if(mysql_real_escape_string(con->con, a2, Escape.c_str(), Escape.length()) == 0)
ret = Escape.c_str();
else
ret = a2;
con->busy = false;
return string(ret);
}
void MySQLDatabase::Shutdown()
{
sLog.outString("sql: Closing all MySQLDatabase connections...");
for(int32 i = 0; i < mConnectionCount; ++i)
{
Disconnect(&Connections[i]);
}
sLog.outString("sql: %u connections closed.", mConnectionCount);
}
bool MySQLDatabase::SendQuery(MysqlCon *con, const char* Sql, bool Self)
{
//dunno what it does ...leaving untouched
int result = mysql_query(con->con, Sql);
if(result > 0)
{
sLog.outDetail("Sql query failed due to [%s], Query: [%s]", mysql_error(con->con ), Sql);
if( Self == false && HandleError(con, mysql_errno(con->con) ) )
{
// 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);
}
}
return (result == 0 ? true : false);
}
bool MySQLDatabase::HandleError(MysqlCon * con, uint32 ErrorNumber)
{
// Handle errors that should cause a reconnect to the MySQLDatabase.
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.
Disconnect(con);
return Connect(con);
}break;
}
return false;
}
QueryResult * MySQLDatabase::Query(const char* QueryString, ...)
{
char sql[16384];
va_list vlist;
va_start(vlist, QueryString);
vsnprintf(sql, 16384, QueryString, vlist);
va_end(vlist);
// Send the query
QueryResult * qResult = NULL;
MysqlCon * con=GetFreeConnection();
if(SendQuery(con, sql, false))
{
// We got a valid query. :)
MYSQL_RES * Result = mysql_store_result(con->con);
// Don't think we're gonna have more than 4 billion rows......
uint32 RowCount = mysql_affected_rows(con->con);
uint32 FieldCount = mysql_field_count(con->con);
// Check if we have no rows.
if(!RowCount || !FieldCount)
{
mysql_free_result(Result);
} else
{
qResult = new MySQLQueryResult( Result, FieldCount, RowCount );
qResult->NextRow();
}
}
con->busy=false;
return qResult;
}
bool MySQLDatabase::Execute(const char* QueryString, ...)
{
char query[16384];
va_list vlist;
va_start(vlist, QueryString);
vsnprintf(query, 16384, QueryString, vlist);
va_end(vlist);
if(!ThreadRunning)
return WaitExecute(query);
int len = strlen(query);
char * pBuffer = new char[len+1];
memcpy(pBuffer, query, len + 1);
queries_queue.push(pBuffer);
return true;
}
//this will wait for completion
bool MySQLDatabase::WaitExecute(const char* QueryString, ...)
{
char sql[16384];
va_list vlist;
va_start(vlist, QueryString);
vsnprintf(sql, 16384, QueryString, vlist);
va_end(vlist);
MysqlCon*con=GetFreeConnection();
bool Result = SendQuery(con, sql, false);
con->busy=false;
return Result;
}
void MySQLDatabase::run()
{
SetThreadName("MySQL Database Execute Thread");
SetThreadState(THREADSTATE_BUSY);
char * query = queries_queue.pop();
while(query)
{
MysqlCon * con=GetFreeConnection();
SendQuery(con,query);
con->busy=false;
delete [] query;
if(ThreadState == THREADSTATE_TERMINATE && queries_queue.size == 0)
break;
query = queries_queue.pop();
}
ThreadRunning = false;
}
MySQLQueryResult::MySQLQueryResult(MYSQL_RES* res, uint32 FieldCount, uint32 RowCount) : QueryResult(FieldCount, RowCount, DATABASE_TYPE_MYSQL), mResult(res)
{
}
MySQLQueryResult::~MySQLQueryResult()
{
}
bool MySQLQueryResult::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;
}
void MySQLQueryResult::Destroy()
{
mysql_free_result(mResult);
mResult = 0;
}
#endif