2017-08-08 13:53:47 +02:00
|
|
|
#include "Database.h"
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
|
|
|
#include <thread>
|
|
|
|
#include <chrono>
|
|
|
|
#include <sstream>
|
|
|
|
|
2017-08-19 00:18:40 +02:00
|
|
|
#include "src/servers/Server_Common/Logging/Logger.h"
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
extern Core::Logger g_log;
|
|
|
|
|
|
|
|
namespace Core {
|
|
|
|
namespace Db {
|
|
|
|
|
|
|
|
QueryResult::QueryResult( MYSQL_RES *res, uint32_t fields, uint32_t rows )
|
|
|
|
: m_result( res ),
|
|
|
|
m_fieldCount( fields ),
|
|
|
|
m_rowCount( rows )
|
|
|
|
{
|
|
|
|
m_currentRow = new Field[fields];
|
|
|
|
}
|
|
|
|
|
|
|
|
QueryResult::~QueryResult()
|
|
|
|
{
|
|
|
|
mysql_free_result( m_result );
|
|
|
|
delete[] m_currentRow;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QueryResult::nextRow()
|
|
|
|
{
|
2017-08-28 18:36:51 +02:00
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
MYSQL_ROW row = mysql_fetch_row( m_result );
|
2017-08-28 18:36:51 +02:00
|
|
|
auto length = mysql_fetch_lengths( m_result );
|
2017-09-13 11:46:17 +02:00
|
|
|
if( row == nullptr )
|
2017-08-08 13:53:47 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
for( uint32_t i = 0; i < m_fieldCount; ++i )
|
|
|
|
{
|
|
|
|
m_currentRow[i].setValue( row[i] );
|
2017-08-28 18:36:51 +02:00
|
|
|
m_currentRow[i].setLength( 0 );
|
|
|
|
if( length )
|
|
|
|
m_currentRow[i].setLength( length[i] );
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
Field *QueryResult::fetch()
|
|
|
|
{
|
|
|
|
return m_currentRow;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t QueryResult::getFieldCount() const
|
|
|
|
{
|
|
|
|
return m_fieldCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t QueryResult::getRowCount() const
|
|
|
|
{
|
|
|
|
return m_rowCount;
|
|
|
|
}
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
Database::Database()
|
|
|
|
{
|
|
|
|
m_port = 0;
|
2017-09-13 11:46:17 +02:00
|
|
|
m_counter = 0;
|
2017-08-08 13:53:47 +02:00
|
|
|
m_pConnections = nullptr;
|
|
|
|
m_connectionCount = -1; // Not connected.
|
|
|
|
}
|
|
|
|
|
|
|
|
Database::~Database()
|
|
|
|
{
|
|
|
|
for( int32_t i = 0; i < m_connectionCount; ++i )
|
|
|
|
{
|
|
|
|
if( m_pConnections[i].conn != nullptr )
|
|
|
|
mysql_close( m_pConnections[i].conn );
|
|
|
|
}
|
|
|
|
|
|
|
|
delete[] m_pConnections;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Database::initialize( const DatabaseParams& params )
|
|
|
|
{
|
|
|
|
uint32_t i;
|
|
|
|
MYSQL * temp;
|
|
|
|
MYSQL * temp2;
|
|
|
|
my_bool my_true = true;
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
g_log.info( "Database: Connecting to " + params.hostname + ", database " + params.databaseName + "..." );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
m_pConnections = new DatabaseConnection[params.connectionCount];
|
|
|
|
for( i = 0; i < params.connectionCount; ++i )
|
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
temp = mysql_init( nullptr );
|
2017-08-08 13:53:47 +02:00
|
|
|
if( mysql_options( temp, MYSQL_SET_CHARSET_NAME, "utf8" ) )
|
2017-09-13 11:46:17 +02:00
|
|
|
g_log.error( "Database: Could not set utf8 character set." );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
if( mysql_options( temp, MYSQL_OPT_RECONNECT, &my_true ) )
|
2017-09-13 11:46:17 +02:00
|
|
|
g_log.error( "Database: MYSQL_OPT_RECONNECT could not be set, "
|
|
|
|
"connection drops may occur but will be counteracted." );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
temp2 = mysql_real_connect( temp,
|
|
|
|
params.hostname.c_str(),
|
|
|
|
params.username.c_str(),
|
|
|
|
params.password.c_str(),
|
|
|
|
params.databaseName.c_str(),
|
|
|
|
params.port,
|
2017-09-13 11:46:17 +02:00
|
|
|
nullptr,
|
2017-08-08 13:53:47 +02:00
|
|
|
0 );
|
2017-09-13 11:46:17 +02:00
|
|
|
if( temp2 == nullptr )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
g_log.fatal( "Database: Connection failed due to: `%s`" + std::string( mysql_error( temp ) ) );
|
2017-08-08 13:53:47 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_pConnections[i].conn = temp2;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t Database::getNextUId()
|
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
execute( std::string( "INSERT INTO uniqueiddata( IdName ) VALUES( 'NOT_SET' );" ) );
|
2017-08-08 13:53:47 +02:00
|
|
|
auto res = query( "SELECT LAST_INSERT_ID();" );
|
|
|
|
|
|
|
|
if( !res )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
Db::Field *field = res->fetch();
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
return field[0].get< uint64_t >();
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
DatabaseConnection * Database::getFreeConnection()
|
|
|
|
{
|
|
|
|
uint32_t i = 0;
|
2017-09-13 11:46:17 +02:00
|
|
|
while( true )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
DatabaseConnection * con = &m_pConnections[( ( i++ ) % m_connectionCount )];
|
|
|
|
if( con->lock.try_lock() )
|
|
|
|
return con;
|
|
|
|
|
|
|
|
// sleep every 20 iterations, otherwise this can cause 100% cpu if the db link goes dead
|
|
|
|
if( !( i % 20 ) )
|
|
|
|
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
boost::shared_ptr< QueryResult > Database::query( const std::string& QueryString )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
// Send the query
|
2017-09-13 11:46:17 +02:00
|
|
|
boost::shared_ptr< QueryResult > qResult( nullptr );
|
2017-08-08 13:53:47 +02:00
|
|
|
DatabaseConnection * con = getFreeConnection();
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
if( sendQuery( con, QueryString.c_str(), false ) )
|
|
|
|
qResult = boost::shared_ptr< QueryResult >( storeQueryResult( con ) );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
con->lock.unlock();
|
|
|
|
return qResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Database::execute( const std::string& QueryString )
|
|
|
|
{
|
|
|
|
DatabaseConnection * con = getFreeConnection();
|
2017-09-13 11:46:17 +02:00
|
|
|
bool Result = sendQuery( con, QueryString, false );
|
2017-08-08 13:53:47 +02:00
|
|
|
con->lock.unlock();
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Database::freeQueryResult( QueryResult * p )
|
|
|
|
{
|
|
|
|
delete p;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Database::escapeString( std::string Escape )
|
|
|
|
{
|
|
|
|
char a2[16384] = { 0 };
|
|
|
|
|
|
|
|
DatabaseConnection * con = getFreeConnection();
|
|
|
|
const char * ret;
|
2017-08-11 22:56:30 +01:00
|
|
|
if( mysql_real_escape_string( con->conn, a2, Escape.c_str(), ( uint32_t ) Escape.length() ) == 0 )
|
2017-08-08 13:53:47 +02:00
|
|
|
ret = Escape.c_str();
|
2017-09-13 11:46:17 +02:00
|
|
|
else
|
2017-08-08 13:53:47 +02:00
|
|
|
ret = a2;
|
|
|
|
|
|
|
|
con->lock.unlock();
|
|
|
|
return std::string( ret );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Database::escapeLongString( const char * str, uint32_t len, std::stringstream& out )
|
|
|
|
{
|
|
|
|
char a2[65536 * 3] = { 0 };
|
|
|
|
|
|
|
|
DatabaseConnection * con = getFreeConnection();
|
2017-08-11 22:56:30 +01:00
|
|
|
mysql_real_escape_string( con->conn, a2, str, ( uint32_t ) len );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
out.write( a2, ( std::streamsize )strlen( a2 ) );
|
|
|
|
con->lock.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Database::escapeString( const char * esc, DatabaseConnection * con )
|
|
|
|
{
|
|
|
|
char a2[16384] = { 0 };
|
|
|
|
const char * ret;
|
2017-08-11 22:56:30 +01:00
|
|
|
if( mysql_real_escape_string( con->conn, a2, ( char* ) esc, ( uint32_t ) strlen( esc ) ) == 0 )
|
2017-08-08 13:53:47 +02:00
|
|
|
ret = esc;
|
|
|
|
else
|
|
|
|
ret = a2;
|
|
|
|
|
|
|
|
return std::string( ret );
|
|
|
|
}
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
bool Database::sendQuery( DatabaseConnection *con, const std::string &sql, bool Self )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
int32_t result = mysql_query( con->conn, sql.c_str() );
|
2017-08-08 13:53:47 +02:00
|
|
|
if( result > 0 )
|
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
if( Self == false && handleError( con, mysql_errno( con->conn ) ) )
|
2017-08-08 13:53:47 +02: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.
|
2017-09-13 11:46:17 +02:00
|
|
|
result = sendQuery(con, sql, true);
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
g_log.error( "Database: query failed " + std::string( mysql_error( con->conn ) ) );
|
|
|
|
g_log.error( "\t" + std::string( sql ) );
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ( result == 0 ? true : false );
|
|
|
|
}
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
bool Database::handleError( DatabaseConnection *con, uint32_t ErrorNumber )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
// Handle errors that should cause a reconnect to the CDatabase.
|
|
|
|
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.
|
2017-09-13 11:46:17 +02:00
|
|
|
return reconnect( con );
|
|
|
|
}
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
QueryResult * Database::storeQueryResult( DatabaseConnection * con )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
QueryResult* res;
|
|
|
|
MYSQL_RES* pRes = mysql_store_result( con->conn );
|
|
|
|
auto uRows = mysql_affected_rows( con->conn );
|
|
|
|
auto uFields = mysql_field_count( con->conn );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
if( uRows == 0 || uFields == 0 || pRes == 0 )
|
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
if( pRes != nullptr )
|
2017-08-08 13:53:47 +02:00
|
|
|
mysql_free_result( pRes );
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
return nullptr;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
res = new QueryResult( pRes,
|
|
|
|
static_cast< uint32_t >( uFields ),
|
|
|
|
static_cast< uint32_t >( uRows ) );
|
2017-08-08 13:53:47 +02:00
|
|
|
res->nextRow();
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
bool Database::reconnect( DatabaseConnection * conn )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
MYSQL * temp;
|
|
|
|
MYSQL * temp2;
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
temp = mysql_init( nullptr );
|
2017-08-08 13:53:47 +02:00
|
|
|
|
|
|
|
temp2 = mysql_real_connect( temp,
|
|
|
|
m_hostname.c_str(),
|
|
|
|
m_username.c_str(),
|
|
|
|
m_password.c_str(),
|
|
|
|
m_databaseName.c_str(),
|
|
|
|
m_port,
|
2017-09-13 11:46:17 +02:00
|
|
|
nullptr,
|
2017-08-08 13:53:47 +02:00
|
|
|
0 );
|
2017-09-13 11:46:17 +02:00
|
|
|
if( temp2 == nullptr )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
g_log.error( "Database: Could not reconnect to database -> " + std::string( mysql_error( temp ) ) );
|
2017-08-08 13:53:47 +02:00
|
|
|
mysql_close( temp );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
if( conn->conn != nullptr )
|
2017-08-08 13:53:47 +02:00
|
|
|
mysql_close( conn->conn );
|
|
|
|
|
|
|
|
conn->conn = temp;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Database::cleanupLibs()
|
|
|
|
{
|
|
|
|
mysql_library_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Database::shutdown()
|
|
|
|
{
|
|
|
|
for( int32_t i = 0; i < m_connectionCount; ++i )
|
|
|
|
{
|
2017-09-13 11:46:17 +02:00
|
|
|
if( m_pConnections[i].conn != nullptr )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
|
|
|
mysql_close( m_pConnections[i].conn );
|
2017-09-13 11:46:17 +02:00
|
|
|
m_pConnections[i].conn = nullptr;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-13 11:46:17 +02:00
|
|
|
const std::string &Database::getHostName()
|
|
|
|
{
|
|
|
|
return m_hostname;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string &Database::getDatabaseName()
|
|
|
|
{
|
|
|
|
return m_databaseName;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Field::setValue( char *value )
|
|
|
|
{
|
|
|
|
m_pValue = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Field::setLength( uint32_t value )
|
|
|
|
{
|
|
|
|
m_size = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Field::getString() const
|
|
|
|
{
|
|
|
|
if( !m_pValue )
|
|
|
|
return "";
|
|
|
|
return std::string( m_pValue );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Field::getBinary( char *dstBuf, uint16_t size ) const
|
|
|
|
{
|
|
|
|
if( m_pValue )
|
|
|
|
memcpy( dstBuf, m_pValue, size );
|
|
|
|
else
|
|
|
|
dstBuf = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
float Field::getFloat() const
|
|
|
|
{
|
|
|
|
return m_pValue ? static_cast< float >( atof( m_pValue ) ) : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Field::getBool() const
|
|
|
|
{
|
|
|
|
return m_pValue ? atoi( m_pValue ) > 0 : false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Field::getLength() const
|
|
|
|
{
|
|
|
|
return m_size;
|
|
|
|
}
|
|
|
|
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
}
|