//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //============================================================================= //#include "misc.h" //#include "stdafx.h" #include ////// MySQL API includes #include #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 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