source-engine/utils/sqlwrapper/sqlwrapper.cpp

454 lines
13 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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