Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. //#include "misc.h"
  8. //#include "stdafx.h"
  9. #include <windows.h>
  10. ////// MySQL API includes
  11. #include <WinSock.H>
  12. #include "mysql.h"
  13. #include "errmsg.h"
  14. #include "platform.h"
  15. #include "isqlwrapper.h"
  16. #include "sqlhelpers.h"
  17. #include "interface.h"
  18. #include "utllinkedlist.h"
  19. #include "utlvector.h"
  20. #include "tier0/memdbgon.h"
  21. ///////
  22. //-----------------------------------------------------------------------------
  23. // Purpose: Main dll entry point
  24. // Input: hModule - our module handle
  25. // dwReason - reason we were called
  26. // lpReserved - bad idea that some Windows developer had some day that
  27. // we're stuck with
  28. //-----------------------------------------------------------------------------
  29. #ifdef _WIN32
  30. BOOL APIENTRY DllMain( HANDLE hModule,
  31. DWORD dwReason,
  32. LPVOID lpReserved
  33. )
  34. {
  35. switch ( dwReason )
  36. {
  37. case DLL_PROCESS_ATTACH:
  38. break;
  39. }
  40. return TRUE;
  41. }
  42. #elif _LINUX
  43. void __attribute__ ((constructor)) app_init(void);
  44. void app_init(void)
  45. {
  46. }
  47. #endif
  48. class CSQLWrapper : public ISQLWrapper, public ISQLHelper
  49. {
  50. public:
  51. CSQLWrapper();
  52. ~CSQLWrapper ();
  53. // ISQLWrapper
  54. virtual void Init( const char *pchDB, const char *pchHost, const char *pchUsername, const char *pchPassword );
  55. virtual bool BInsert( const char *pchQueryString );
  56. virtual const ISQLTableSet *PSQLTableSetDescription();
  57. virtual IResultSet *PResultSetQuery( const char *pchQueryString );
  58. virtual void FreeResult();
  59. // ISQLHelper
  60. virtual bool BInternalQuery( const char *pchQueryString, MYSQL_RES **ppMySQLRes, bool bRecurse /* = true */ );
  61. #ifdef DBGFLAG_VALIDATE
  62. void Validate( CValidator &validator, char *pchName ); // Validate our internal structures
  63. #endif
  64. private:
  65. bool BConnect();
  66. void Disconnect();
  67. bool _Query( const char *pchQueryString, MYSQL_RES **result );
  68. char *m_pchDB;
  69. char *m_pchHost;
  70. char *m_pchUsername;
  71. char *m_pchPassword;
  72. bool m_bConnected;
  73. MYSQL m_MySQL;
  74. CSQLTableSet m_SQLTableSet;
  75. CResultSet m_ResultSet;
  76. bool m_bInQuery;
  77. };
  78. class CSQLWrapperFactory : public ISQLWrapperFactory
  79. {
  80. public:
  81. CSQLWrapperFactory() {};
  82. ~CSQLWrapperFactory() {};
  83. virtual ISQLWrapper *Create( const char *pchDB, const char *pchHost, const char *pchUsername, const char *pchPassword );
  84. virtual void Free( ISQLWrapper *pWrapper );
  85. #ifdef DBGFLAG_VALIDATE
  86. void Validate( CValidator &validator, char *pchName ); // Validate our internal structures
  87. #endif
  88. private:
  89. CUtlFixedLinkedList<CSQLWrapper> m_ListSQLWrapper; // use a fixed in memory data struct so we can return pointers to the interfaces
  90. };
  91. CSQLWrapperFactory g_SQLWrapperFactory;
  92. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CSQLWrapperFactory, ISQLWrapperFactory, INTERFACEVERSION_ISQLWRAPPER, g_SQLWrapperFactory );
  93. #if 0
  94. //-----------------------------------------------------------------------------
  95. // Purpose: Ensure that all of our internal structures are consistent, and
  96. // account for all memory that we've allocated.
  97. // Input: validator - Our global validator object
  98. //-----------------------------------------------------------------------------
  99. class CDLLValidate : public IValidate
  100. {
  101. public:
  102. virtual void Validate( CValidator & validator )
  103. {
  104. #ifdef DBGFLAG_VALIDATE
  105. g_SQLWrapperFactory.Validate( validator, "g_SQLWrapperFactory" );
  106. #endif
  107. }
  108. };
  109. CDLLValidate g_DLLValidate;
  110. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CDLLValidate, IValidate, INTERFACEVERSION_IVALIDATE, g_DLLValidate );
  111. #endif
  112. //-----------------------------------------------------------------------------
  113. // Purpose: Create a SQLWrapper interface to use
  114. // Input: pchDB - database name to connect to
  115. // pchHost - host to connect to
  116. // pchUsername - username to connect as
  117. // pchPassword - password to use
  118. // Output: a pointer to a sql wrapper interface
  119. //-----------------------------------------------------------------------------
  120. ISQLWrapper *CSQLWrapperFactory::Create( const char *pchDB, const char *pchHost, const char *pchUsername, const char *pchPassword )
  121. {
  122. int iSQLWrapper = m_ListSQLWrapper.AddToTail();
  123. CSQLWrapper &sqlWrapper = m_ListSQLWrapper[iSQLWrapper];
  124. sqlWrapper.Init( pchDB, pchHost, pchUsername, pchPassword );
  125. return &sqlWrapper;
  126. }
  127. //-----------------------------------------------------------------------------
  128. // Purpose: Free a previously allocated sql interface
  129. // Input: pWrapper - interface that was alloced
  130. //-----------------------------------------------------------------------------
  131. void CSQLWrapperFactory::Free( ISQLWrapper *pSQLWrapper )
  132. {
  133. FOR_EACH_LL( m_ListSQLWrapper, iSQLWrapper )
  134. {
  135. if ( &m_ListSQLWrapper[iSQLWrapper] == ((CSQLWrapper *)pSQLWrapper) )
  136. {
  137. m_ListSQLWrapper.Remove(iSQLWrapper);
  138. break;
  139. }
  140. }
  141. Assert( iSQLWrapper != m_ListSQLWrapper.InvalidIndex() );
  142. }
  143. //-----------------------------------------------------------------------------
  144. // Purpose: Ensure that all of our internal structures are consistent, and
  145. // account for all memory that we've allocated.
  146. // Input: validator - Our global validator object
  147. // pchName - Our name (typically a member var in our container)
  148. //-----------------------------------------------------------------------------
  149. #ifdef DBGFLAG_VALIDATE
  150. void CSQLWrapperFactory::Validate( CValidator &validator, char *pchName )
  151. {
  152. validator.Push( "CSQLWrapperFactory", this, pchName );
  153. m_ListSQLWrapper.Validate( validator, "m_ListSQLWrapper" );
  154. FOR_EACH_LL( m_ListSQLWrapper, iListSQLWrapper )
  155. {
  156. m_ListSQLWrapper[ iListSQLWrapper ].Validate( validator, "m_ListSQLWrapper[ iListSQLWrapper ]" );
  157. }
  158. validator.Pop();
  159. }
  160. #endif
  161. #define SAFE_DELETE( pointer ) if ( pointer != NULL ) { delete pointer; }
  162. //-----------------------------------------------------------------------------
  163. // Purpose: Constructor.
  164. //-----------------------------------------------------------------------------
  165. CSQLWrapper::CSQLWrapper()
  166. {
  167. m_pchDB = NULL;
  168. m_pchHost = NULL;
  169. m_pchUsername = NULL;
  170. m_pchPassword = NULL;
  171. m_bConnected = false;
  172. m_bInQuery = false;
  173. mysql_init( &m_MySQL );
  174. }
  175. //-----------------------------------------------------------------------------
  176. // Purpose: Destructor.
  177. //-----------------------------------------------------------------------------
  178. CSQLWrapper::~CSQLWrapper()
  179. {
  180. FreeResult();
  181. mysql_close( &m_MySQL );
  182. m_bConnected = false;
  183. SAFE_DELETE(m_pchDB);
  184. SAFE_DELETE(m_pchHost);
  185. SAFE_DELETE(m_pchUsername);
  186. SAFE_DELETE(m_pchPassword);
  187. }
  188. // helper macro to alloc and copy a string to a member var
  189. // BUGBUG Alfred: Make this a Q_function
  190. #define COPY_STRING( dst, src ) \
  191. dst = new char[Q_strlen(src) + 1]; \
  192. Assert( dst ); \
  193. Q_strncpy( dst, src, Q_strlen(src) + 1); \
  194. dst[ Q_strlen(src) ] = 0;
  195. //-----------------------------------------------------------------------------
  196. // Purpose: Initializer. Sets up connection params but DOESN'T actually do the connection
  197. // Input: pchDB - database name to connect to
  198. // pchHost - host to connect to
  199. // pchUsername - username to connect as
  200. // pchPassword - password to use
  201. //-----------------------------------------------------------------------------
  202. void CSQLWrapper::Init( const char *pchDB, const char *pchHost, const char *pchUsername, const char *pchPassword )
  203. {
  204. COPY_STRING( m_pchDB, pchDB );
  205. COPY_STRING( m_pchHost, pchHost );
  206. COPY_STRING( m_pchUsername, pchUsername );
  207. COPY_STRING( m_pchPassword, pchPassword );
  208. }
  209. //-----------------------------------------------------------------------------
  210. // Purpose: Connects to a MySQL server.
  211. // Output: true if connect works, false otherwise
  212. //-----------------------------------------------------------------------------
  213. bool CSQLWrapper::BConnect()
  214. {
  215. m_bInQuery = false;
  216. MYSQL *pMYSQL = mysql_real_connect( &m_MySQL, m_pchHost, m_pchUsername, m_pchPassword, m_pchDB, 0, NULL, 0);
  217. if ( !pMYSQL || pMYSQL != &m_MySQL ) // on success we get our SQL pointer back
  218. {
  219. DevMsg( "Failed to connect to DB server (%s)\n", mysql_error(&m_MySQL) );
  220. return false;
  221. }
  222. m_bConnected = true;
  223. return true;
  224. }
  225. //-----------------------------------------------------------------------------
  226. // Purpose: Disconnects from a mysql if you are already connected
  227. //-----------------------------------------------------------------------------
  228. void CSQLWrapper::Disconnect()
  229. {
  230. mysql_close( &m_MySQL );
  231. mysql_init( &m_MySQL );
  232. m_bConnected = false;
  233. }
  234. //-----------------------------------------------------------------------------
  235. // Purpose: run a query on the db with retries
  236. // Input: pchQueryString - query string to run
  237. // ppMySQLRes - mysql result set to set
  238. // bRecurse - if true allow this function to call itself again (to rety the query)
  239. // Output: true if query succeeds, false otherwise
  240. //-----------------------------------------------------------------------------
  241. bool CSQLWrapper::BInternalQuery( const char *pchQueryString, MYSQL_RES **ppMySQLRes, bool bRecurse )
  242. {
  243. *ppMySQLRes = NULL;
  244. if ( !m_pchDB || !m_pchHost || !m_pchUsername || !m_pchPassword || !ppMySQLRes )
  245. {
  246. return false;
  247. }
  248. if ( !m_bConnected )
  249. {
  250. BConnect(); // need to reconnect
  251. }
  252. bool bRet = _Query( pchQueryString, ppMySQLRes );
  253. if ( !bRet && !m_bConnected && bRecurse ) // hmmm, got hung up when running the query
  254. {
  255. bRet = BInternalQuery( pchQueryString, ppMySQLRes, false ); // run the query again now we reconnected
  256. }
  257. return bRet;
  258. }
  259. //-----------------------------------------------------------------------------
  260. // Purpose: Actually runs a query on the server
  261. // Input: pchQueryString - query string to run
  262. // result - mysql result set to set
  263. // Output: true if query succeeds, false otherwise, result it set on success
  264. //-----------------------------------------------------------------------------
  265. bool CSQLWrapper::_Query( const char *pchQueryString, MYSQL_RES **ppMySQLRes )
  266. {
  267. *ppMySQLRes = NULL;
  268. int iResult = mysql_real_query( &m_MySQL, pchQueryString, Q_strlen(pchQueryString) );
  269. if ( iResult != 0 )
  270. {
  271. int iErrNo = mysql_errno(&m_MySQL);
  272. if ( iErrNo == CR_COMMANDS_OUT_OF_SYNC ) // I hate this return code, you just need to hang up and try again
  273. {
  274. Disconnect();
  275. return false;
  276. }
  277. else if ( iErrNo == CR_SERVER_LOST || iErrNo == CR_SERVER_GONE_ERROR )
  278. {
  279. m_bConnected = false;
  280. return false;
  281. }
  282. else if ( iErrNo != 0 )
  283. {
  284. DevMsg( "CSQLWrapper::_Query Generic SQL query error: %s\n", mysql_error( &m_MySQL ) );
  285. return false;
  286. }
  287. }
  288. if ( mysql_field_count( &m_MySQL ) > 0 ) // if there are results clear them from the connection
  289. {
  290. *ppMySQLRes = mysql_store_result( &m_MySQL );
  291. }
  292. return true;
  293. }
  294. //-----------------------------------------------------------------------------
  295. // Purpose: Runs a insert style query on the db (i.e no return set), opens the connection if it isn't currently
  296. // Input: pchQueryString - query string to run
  297. // Output: true if query succeeds, false otherwise
  298. //-----------------------------------------------------------------------------
  299. bool CSQLWrapper::BInsert( const char *pchQueryString )
  300. {
  301. if ( m_bInQuery )
  302. {
  303. Assert( !m_bInQuery );
  304. return false;
  305. }
  306. m_bInQuery = true;
  307. MYSQL_RES *pMySQLRes = NULL;
  308. bool bRet = BInternalQuery( pchQueryString, &pMySQLRes, true );
  309. if ( bRet && pMySQLRes )
  310. {
  311. mysql_free_result( pMySQLRes );
  312. }
  313. m_bInQuery = false;
  314. return bRet;
  315. }
  316. //-----------------------------------------------------------------------------
  317. // Purpose: returns a pointer to the table descriptions for this DB
  318. // Output: table description pointer
  319. //-----------------------------------------------------------------------------
  320. const ISQLTableSet *CSQLWrapper::PSQLTableSetDescription()
  321. {
  322. if ( m_bInQuery )
  323. {
  324. Assert( !m_bInQuery );
  325. return NULL;
  326. }
  327. m_bInQuery = true;
  328. if ( !m_SQLTableSet.Init( this ) )
  329. {
  330. return NULL;
  331. }
  332. m_bInQuery = false;
  333. return &m_SQLTableSet;
  334. }
  335. //-----------------------------------------------------------------------------
  336. // Purpose: Runs a select style query on the db (i.e a return set), opens the connection if it isn't currently
  337. // Input: pchQueryString - query string to run
  338. // Output: returns a pointer to the result set (NULL on failure)
  339. //-----------------------------------------------------------------------------
  340. IResultSet *CSQLWrapper::PResultSetQuery( const char *pchQueryString )
  341. {
  342. if ( m_bInQuery )
  343. {
  344. Assert( !m_bInQuery );
  345. return NULL;
  346. }
  347. bool bRet = m_ResultSet.Query( pchQueryString, this );
  348. if ( !bRet )
  349. {
  350. return NULL;
  351. }
  352. m_bInQuery = true;
  353. return &m_ResultSet;
  354. }
  355. //-----------------------------------------------------------------------------
  356. // Purpose: Free's any currently running result set
  357. //-----------------------------------------------------------------------------
  358. void CSQLWrapper::FreeResult()
  359. {
  360. m_bInQuery = false;
  361. m_ResultSet.FreeResult();
  362. }
  363. //-----------------------------------------------------------------------------
  364. // Purpose: Ensure that all of our internal structures are consistent, and
  365. // account for all memory that we've allocated.
  366. // Input: validator - Our global validator object
  367. // pchName - Our name (typically a member var in our container)
  368. //-----------------------------------------------------------------------------
  369. #ifdef DBGFLAG_VALIDATE
  370. void CSQLWrapper::Validate( CValidator &validator, char *pchName )
  371. {
  372. validator.Push( "CSQLWrapper", this, pchName );
  373. validator.ClaimMemory( m_pchDB );
  374. validator.ClaimMemory( m_pchHost );
  375. validator.ClaimMemory( m_pchUsername );
  376. validator.ClaimMemory( m_pchPassword );
  377. m_SQLTableSet.Validate( validator, "m_SQLTableSet" );
  378. m_ResultSet.Validate( validator, "m_ResultSet" );
  379. validator.Pop();
  380. }
  381. #endif