You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
13 KiB
453 lines
13 KiB
//========= 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
|
|
|