454 lines
13 KiB
C++
454 lines
13 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================
|
|
//#include "misc.h"
|
|
//#include "stdafx.h"
|
|
|
|
#include <windows.h>
|
|
|
|
////// MySQL API includes
|
|
#include <WinSock.H>
|
|
#include "mysql.h"
|
|
#include "errmsg.h"
|
|
|
|
|
|
#include "platform.h"
|
|
#include "isqlwrapper.h"
|
|
#include "sqlhelpers.h"
|
|
#include "interface.h"
|
|
#include "utllinkedlist.h"
|
|
#include "utlvector.h"
|
|
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
///////
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Main dll entry point
|
|
// Input: hModule - our module handle
|
|
// dwReason - reason we were called
|
|
// lpReserved - bad idea that some Windows developer had some day that
|
|
// we're stuck with
|
|
//-----------------------------------------------------------------------------
|
|
#ifdef _WIN32
|
|
BOOL APIENTRY DllMain( HANDLE hModule,
|
|
DWORD dwReason,
|
|
LPVOID lpReserved
|
|
)
|
|
{
|
|
switch ( dwReason )
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#elif _LINUX
|
|
void __attribute__ ((constructor)) app_init(void);
|
|
void app_init(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
|
|
class CSQLWrapper : public ISQLWrapper, public ISQLHelper
|
|
{
|
|
public:
|
|
CSQLWrapper();
|
|
~CSQLWrapper ();
|
|
|
|
// ISQLWrapper
|
|
virtual void Init( const char *pchDB, const char *pchHost, const char *pchUsername, const char *pchPassword );
|
|
virtual bool BInsert( const char *pchQueryString );
|
|
virtual const ISQLTableSet *PSQLTableSetDescription();
|
|
virtual IResultSet *PResultSetQuery( const char *pchQueryString );
|
|
virtual void FreeResult();
|
|
|
|
// ISQLHelper
|
|
virtual bool BInternalQuery( const char *pchQueryString, MYSQL_RES **ppMySQLRes, bool bRecurse /* = true */ );
|
|
|
|
#ifdef DBGFLAG_VALIDATE
|
|
void Validate( CValidator &validator, char *pchName ); // Validate our internal structures
|
|
#endif
|
|
|
|
private:
|
|
bool BConnect();
|
|
void Disconnect();
|
|
bool _Query( const char *pchQueryString, MYSQL_RES **result );
|
|
|
|
char *m_pchDB;
|
|
char *m_pchHost;
|
|
char *m_pchUsername;
|
|
char *m_pchPassword;
|
|
bool m_bConnected;
|
|
|
|
MYSQL m_MySQL;
|
|
CSQLTableSet m_SQLTableSet;
|
|
CResultSet m_ResultSet;
|
|
bool m_bInQuery;
|
|
};
|
|
|
|
|
|
class CSQLWrapperFactory : public ISQLWrapperFactory
|
|
{
|
|
public:
|
|
CSQLWrapperFactory() {};
|
|
~CSQLWrapperFactory() {};
|
|
|
|
virtual ISQLWrapper *Create( const char *pchDB, const char *pchHost, const char *pchUsername, const char *pchPassword );
|
|
virtual void Free( ISQLWrapper *pWrapper );
|
|
|
|
#ifdef DBGFLAG_VALIDATE
|
|
void Validate( CValidator &validator, char *pchName ); // Validate our internal structures
|
|
#endif
|
|
|
|
private:
|
|
CUtlFixedLinkedList<CSQLWrapper> m_ListSQLWrapper; // use a fixed in memory data struct so we can return pointers to the interfaces
|
|
};
|
|
|
|
CSQLWrapperFactory g_SQLWrapperFactory;
|
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CSQLWrapperFactory, ISQLWrapperFactory, INTERFACEVERSION_ISQLWRAPPER, g_SQLWrapperFactory );
|
|
|
|
#if 0
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Ensure that all of our internal structures are consistent, and
|
|
// account for all memory that we've allocated.
|
|
// Input: validator - Our global validator object
|
|
//-----------------------------------------------------------------------------
|
|
class CDLLValidate : public IValidate
|
|
{
|
|
public:
|
|
virtual void Validate( CValidator & validator )
|
|
{
|
|
#ifdef DBGFLAG_VALIDATE
|
|
g_SQLWrapperFactory.Validate( validator, "g_SQLWrapperFactory" );
|
|
#endif
|
|
}
|
|
};
|
|
CDLLValidate g_DLLValidate;
|
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CDLLValidate, IValidate, INTERFACEVERSION_IVALIDATE, g_DLLValidate );
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Create a SQLWrapper interface to use
|
|
// Input: pchDB - database name to connect to
|
|
// pchHost - host to connect to
|
|
// pchUsername - username to connect as
|
|
// pchPassword - password to use
|
|
// Output: a pointer to a sql wrapper interface
|
|
//-----------------------------------------------------------------------------
|
|
ISQLWrapper *CSQLWrapperFactory::Create( const char *pchDB, const char *pchHost, const char *pchUsername, const char *pchPassword )
|
|
{
|
|
int iSQLWrapper = m_ListSQLWrapper.AddToTail();
|
|
CSQLWrapper &sqlWrapper = m_ListSQLWrapper[iSQLWrapper];
|
|
sqlWrapper.Init( pchDB, pchHost, pchUsername, pchPassword );
|
|
return &sqlWrapper;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Free a previously allocated sql interface
|
|
// Input: pWrapper - interface that was alloced
|
|
//-----------------------------------------------------------------------------
|
|
void CSQLWrapperFactory::Free( ISQLWrapper *pSQLWrapper )
|
|
{
|
|
FOR_EACH_LL( m_ListSQLWrapper, iSQLWrapper )
|
|
{
|
|
if ( &m_ListSQLWrapper[iSQLWrapper] == ((CSQLWrapper *)pSQLWrapper) )
|
|
{
|
|
m_ListSQLWrapper.Remove(iSQLWrapper);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert( iSQLWrapper != m_ListSQLWrapper.InvalidIndex() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Ensure that all of our internal structures are consistent, and
|
|
// account for all memory that we've allocated.
|
|
// Input: validator - Our global validator object
|
|
// pchName - Our name (typically a member var in our container)
|
|
//-----------------------------------------------------------------------------
|
|
#ifdef DBGFLAG_VALIDATE
|
|
void CSQLWrapperFactory::Validate( CValidator &validator, char *pchName )
|
|
{
|
|
validator.Push( "CSQLWrapperFactory", this, pchName );
|
|
|
|
m_ListSQLWrapper.Validate( validator, "m_ListSQLWrapper" );
|
|
|
|
FOR_EACH_LL( m_ListSQLWrapper, iListSQLWrapper )
|
|
{
|
|
m_ListSQLWrapper[ iListSQLWrapper ].Validate( validator, "m_ListSQLWrapper[ iListSQLWrapper ]" );
|
|
}
|
|
|
|
validator.Pop();
|
|
}
|
|
#endif
|
|
|
|
#define SAFE_DELETE( pointer ) if ( pointer != NULL ) { delete pointer; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Constructor.
|
|
//-----------------------------------------------------------------------------
|
|
CSQLWrapper::CSQLWrapper()
|
|
{
|
|
m_pchDB = NULL;
|
|
m_pchHost = NULL;
|
|
m_pchUsername = NULL;
|
|
m_pchPassword = NULL;
|
|
m_bConnected = false;
|
|
m_bInQuery = false;
|
|
|
|
mysql_init( &m_MySQL );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Destructor.
|
|
//-----------------------------------------------------------------------------
|
|
CSQLWrapper::~CSQLWrapper()
|
|
{
|
|
FreeResult();
|
|
mysql_close( &m_MySQL );
|
|
m_bConnected = false;
|
|
|
|
SAFE_DELETE(m_pchDB);
|
|
SAFE_DELETE(m_pchHost);
|
|
SAFE_DELETE(m_pchUsername);
|
|
SAFE_DELETE(m_pchPassword);
|
|
}
|
|
|
|
// helper macro to alloc and copy a string to a member var
|
|
// BUGBUG Alfred: Make this a Q_function
|
|
#define COPY_STRING( dst, src ) \
|
|
dst = new char[Q_strlen(src) + 1]; \
|
|
Assert( dst ); \
|
|
Q_strncpy( dst, src, Q_strlen(src) + 1); \
|
|
dst[ Q_strlen(src) ] = 0;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Initializer. Sets up connection params but DOESN'T actually do the connection
|
|
// Input: pchDB - database name to connect to
|
|
// pchHost - host to connect to
|
|
// pchUsername - username to connect as
|
|
// pchPassword - password to use
|
|
//-----------------------------------------------------------------------------
|
|
void CSQLWrapper::Init( const char *pchDB, const char *pchHost, const char *pchUsername, const char *pchPassword )
|
|
{
|
|
COPY_STRING( m_pchDB, pchDB );
|
|
COPY_STRING( m_pchHost, pchHost );
|
|
COPY_STRING( m_pchUsername, pchUsername );
|
|
COPY_STRING( m_pchPassword, pchPassword );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Connects to a MySQL server.
|
|
// Output: true if connect works, false otherwise
|
|
//-----------------------------------------------------------------------------
|
|
bool CSQLWrapper::BConnect()
|
|
{
|
|
m_bInQuery = false;
|
|
|
|
MYSQL *pMYSQL = mysql_real_connect( &m_MySQL, m_pchHost, m_pchUsername, m_pchPassword, m_pchDB, 0, NULL, 0);
|
|
if ( !pMYSQL || pMYSQL != &m_MySQL ) // on success we get our SQL pointer back
|
|
{
|
|
DevMsg( "Failed to connect to DB server (%s)\n", mysql_error(&m_MySQL) );
|
|
return false;
|
|
}
|
|
m_bConnected = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Disconnects from a mysql if you are already connected
|
|
//-----------------------------------------------------------------------------
|
|
void CSQLWrapper::Disconnect()
|
|
{
|
|
mysql_close( &m_MySQL );
|
|
mysql_init( &m_MySQL );
|
|
m_bConnected = false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: run a query on the db with retries
|
|
// Input: pchQueryString - query string to run
|
|
// ppMySQLRes - mysql result set to set
|
|
// bRecurse - if true allow this function to call itself again (to rety the query)
|
|
// Output: true if query succeeds, false otherwise
|
|
//-----------------------------------------------------------------------------
|
|
bool CSQLWrapper::BInternalQuery( const char *pchQueryString, MYSQL_RES **ppMySQLRes, bool bRecurse )
|
|
{
|
|
*ppMySQLRes = NULL;
|
|
if ( !m_pchDB || !m_pchHost || !m_pchUsername || !m_pchPassword || !ppMySQLRes )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( !m_bConnected )
|
|
{
|
|
BConnect(); // need to reconnect
|
|
}
|
|
|
|
bool bRet = _Query( pchQueryString, ppMySQLRes );
|
|
if ( !bRet && !m_bConnected && bRecurse ) // hmmm, got hung up when running the query
|
|
{
|
|
bRet = BInternalQuery( pchQueryString, ppMySQLRes, false ); // run the query again now we reconnected
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Actually runs a query on the server
|
|
// Input: pchQueryString - query string to run
|
|
// result - mysql result set to set
|
|
// Output: true if query succeeds, false otherwise, result it set on success
|
|
//-----------------------------------------------------------------------------
|
|
bool CSQLWrapper::_Query( const char *pchQueryString, MYSQL_RES **ppMySQLRes )
|
|
{
|
|
*ppMySQLRes = NULL;
|
|
int iResult = mysql_real_query( &m_MySQL, pchQueryString, Q_strlen(pchQueryString) );
|
|
if ( iResult != 0 )
|
|
{
|
|
int iErrNo = mysql_errno(&m_MySQL);
|
|
if ( iErrNo == CR_COMMANDS_OUT_OF_SYNC ) // I hate this return code, you just need to hang up and try again
|
|
{
|
|
Disconnect();
|
|
return false;
|
|
}
|
|
else if ( iErrNo == CR_SERVER_LOST || iErrNo == CR_SERVER_GONE_ERROR )
|
|
{
|
|
m_bConnected = false;
|
|
return false;
|
|
}
|
|
else if ( iErrNo != 0 )
|
|
{
|
|
DevMsg( "CSQLWrapper::_Query Generic SQL query error: %s\n", mysql_error( &m_MySQL ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( mysql_field_count( &m_MySQL ) > 0 ) // if there are results clear them from the connection
|
|
{
|
|
*ppMySQLRes = mysql_store_result( &m_MySQL );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Runs a insert style query on the db (i.e no return set), opens the connection if it isn't currently
|
|
// Input: pchQueryString - query string to run
|
|
// Output: true if query succeeds, false otherwise
|
|
//-----------------------------------------------------------------------------
|
|
bool CSQLWrapper::BInsert( const char *pchQueryString )
|
|
{
|
|
if ( m_bInQuery )
|
|
{
|
|
Assert( !m_bInQuery );
|
|
return false;
|
|
}
|
|
|
|
m_bInQuery = true;
|
|
MYSQL_RES *pMySQLRes = NULL;
|
|
bool bRet = BInternalQuery( pchQueryString, &pMySQLRes, true );
|
|
if ( bRet && pMySQLRes )
|
|
{
|
|
mysql_free_result( pMySQLRes );
|
|
}
|
|
m_bInQuery = false;
|
|
return bRet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: returns a pointer to the table descriptions for this DB
|
|
// Output: table description pointer
|
|
//-----------------------------------------------------------------------------
|
|
const ISQLTableSet *CSQLWrapper::PSQLTableSetDescription()
|
|
{
|
|
if ( m_bInQuery )
|
|
{
|
|
Assert( !m_bInQuery );
|
|
return NULL;
|
|
}
|
|
|
|
m_bInQuery = true;
|
|
if ( !m_SQLTableSet.Init( this ) )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
m_bInQuery = false;
|
|
return &m_SQLTableSet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Runs a select style query on the db (i.e a return set), opens the connection if it isn't currently
|
|
// Input: pchQueryString - query string to run
|
|
// Output: returns a pointer to the result set (NULL on failure)
|
|
//-----------------------------------------------------------------------------
|
|
IResultSet *CSQLWrapper::PResultSetQuery( const char *pchQueryString )
|
|
{
|
|
if ( m_bInQuery )
|
|
{
|
|
Assert( !m_bInQuery );
|
|
return NULL;
|
|
}
|
|
|
|
bool bRet = m_ResultSet.Query( pchQueryString, this );
|
|
if ( !bRet )
|
|
{
|
|
return NULL;
|
|
}
|
|
m_bInQuery = true;
|
|
return &m_ResultSet;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Free's any currently running result set
|
|
//-----------------------------------------------------------------------------
|
|
void CSQLWrapper::FreeResult()
|
|
{
|
|
m_bInQuery = false;
|
|
m_ResultSet.FreeResult();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Ensure that all of our internal structures are consistent, and
|
|
// account for all memory that we've allocated.
|
|
// Input: validator - Our global validator object
|
|
// pchName - Our name (typically a member var in our container)
|
|
//-----------------------------------------------------------------------------
|
|
#ifdef DBGFLAG_VALIDATE
|
|
void CSQLWrapper::Validate( CValidator &validator, char *pchName )
|
|
{
|
|
validator.Push( "CSQLWrapper", this, pchName );
|
|
|
|
validator.ClaimMemory( m_pchDB );
|
|
validator.ClaimMemory( m_pchHost );
|
|
validator.ClaimMemory( m_pchUsername );
|
|
validator.ClaimMemory( m_pchPassword );
|
|
|
|
m_SQLTableSet.Validate( validator, "m_SQLTableSet" );
|
|
m_ResultSet.Validate( validator, "m_ResultSet" );
|
|
|
|
validator.Pop();
|
|
}
|
|
#endif
|