|
|
//========= 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
|