PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/hphp/util/db-conn.cpp

http://github.com/facebook/hiphop-php
C++ | 463 lines | 381 code | 60 blank | 22 comment | 51 complexity | d008f717d47d3eea4c74128213180492 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-2-Clause, BSD-3-Clause, MPL-2.0-no-copyleft-exception, MIT, LGPL-2.0, Apache-2.0
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. */
  16. #include "hphp/util/db-conn.h"
  17. #include "hphp/util/db-query.h"
  18. #include "hphp/util/db-mysql.h"
  19. #include "hphp/util/exception.h"
  20. #include "hphp/util/lock.h"
  21. #include "hphp/util/async-job.h"
  22. #include "util.h"
  23. #include "hphp/util/alloc.h"
  24. #include <boost/lexical_cast.hpp>
  25. namespace HPHP {
  26. ///////////////////////////////////////////////////////////////////////////////
  27. DatabaseException::DatabaseException(int code,
  28. const char *fmt, ...) : m_code(code) {
  29. va_list ap; va_start(ap, fmt); format(fmt, ap); va_end(ap);
  30. }
  31. // Class ServerData
  32. int ServerData::DefaultPort = 3306;
  33. std::string ServerData::DefaultUsername = "root";
  34. std::string ServerData::DefaultPassword = "";
  35. class DBConnQueryJob {
  36. public:
  37. DBConnQueryJob(ServerDataPtr server, const std::string sql, int index,
  38. Mutex &mutex, DBDataSet &dsResult, bool retryQueryOnFail,
  39. unsigned int readTimeout, unsigned int connectTimeout,
  40. int maxRetryOpenOnFail, int maxRetryQueryOnFail)
  41. : m_server(server), m_sql(sql), m_index(index),
  42. m_affected(0), m_dsMutex(&mutex), m_dsResult(&dsResult),
  43. m_retryQueryOnFail(retryQueryOnFail), m_connectTimeout(connectTimeout),
  44. m_readTimeout(readTimeout),
  45. m_maxRetryOpenOnFail(maxRetryOpenOnFail),
  46. m_maxRetryQueryOnFail(maxRetryQueryOnFail) {}
  47. ServerDataPtr m_server;
  48. std::string m_sql;
  49. int m_index;
  50. int m_affected;
  51. Mutex *m_dsMutex;
  52. DBDataSet *m_dsResult;
  53. DBConn::ErrorInfo m_error;
  54. bool m_retryQueryOnFail;
  55. int m_connectTimeout;
  56. int m_readTimeout;
  57. int m_maxRetryOpenOnFail;
  58. int m_maxRetryQueryOnFail;
  59. };
  60. class DBConnQueryWorker {
  61. public:
  62. void onThreadEnter() {}
  63. void doJob(DBConnQueryJobPtr job);
  64. void onThreadExit() { mysql_thread_end();}
  65. };
  66. static void parseColonPair(std::string &s, size_t pos,
  67. std::string &part1, std::string &part2) {
  68. string tmp = s.substr(0, pos);
  69. s = s.substr(pos + 1);
  70. pos = tmp.find(':');
  71. if (pos == string::npos) {
  72. part1 = tmp;
  73. } else {
  74. part1 = tmp.substr(0, pos);
  75. part2 = tmp.substr(pos + 1);
  76. }
  77. }
  78. ServerDataPtr ServerData::Create(const std::string &connection) {
  79. ServerDataPtr server(new ServerData());
  80. string s = connection;
  81. size_t pos = s.find('@');
  82. if (pos != string::npos) {
  83. parseColonPair(s, pos, server->m_username, server->m_password);
  84. }
  85. pos = s.find('/');
  86. if (pos != string::npos) {
  87. string port;
  88. parseColonPair(s, pos, server->m_ip, port);
  89. if (!port.empty()) server->m_port = atoi(port.c_str());
  90. }
  91. server->m_database = s;
  92. return server;
  93. }
  94. ServerData::ServerData() : m_port(0) {
  95. }
  96. ServerData::ServerData(const char *ip, const char *database,
  97. int port, const char *username,
  98. const char *password,
  99. const SessionVariableVec &sessionVariables) :
  100. m_port(port), m_sessionVariables(sessionVariables) {
  101. if (ip) m_ip = ip;
  102. if (database) m_database = database;
  103. if (username) m_username = username;
  104. if (password) m_password = password;
  105. }
  106. int ServerData::getPort() const {
  107. return m_port > 0 ? m_port : DefaultPort;
  108. }
  109. const std::string &ServerData::getUserName() const {
  110. return m_username.empty() ? DefaultUsername : m_username;
  111. }
  112. const std::string &ServerData::getPassword() const {
  113. return m_password.empty() ? DefaultPassword : m_password;
  114. }
  115. ///////////////////////////////////////////////////////////////////////////////
  116. // static members
  117. unsigned int DBConn::DefaultWorkerCount = 50;
  118. unsigned int DBConn::DefaultConnectTimeout = 1000;
  119. unsigned int DBConn::DefaultReadTimeout = 1000;
  120. Mutex DBConn::s_mutex;
  121. DBConn::DatabaseMap DBConn::s_localDatabases;
  122. void DBConn::ClearLocalDatabases() {
  123. Lock lock(s_mutex);
  124. s_localDatabases.clear();
  125. }
  126. void DBConn::AddLocalDB(int dbId, const char *ip, const char *db,
  127. int port, const char *username, const char *password,
  128. const SessionVariableVec &sessionVariables) {
  129. Lock lock(s_mutex);
  130. s_localDatabases[dbId] =
  131. ServerDataPtr(new ServerData(ip, db, port, username, password,
  132. sessionVariables));
  133. }
  134. ///////////////////////////////////////////////////////////////////////////////
  135. DBConn::DBConn(int maxRetryOpenOnFail, int maxRetryQueryOnFail)
  136. : m_conn(nullptr), m_connectTimeout(DefaultConnectTimeout),
  137. m_readTimeout(DefaultReadTimeout),
  138. m_maxRetryOpenOnFail(maxRetryOpenOnFail) {
  139. }
  140. DBConn::~DBConn() {
  141. close();
  142. }
  143. void DBConn::open(ServerDataPtr server, int connectTimeout /* = -1 */,
  144. int readTimeout /* = -1 */) {
  145. if (isOpened()) {
  146. close();
  147. }
  148. if (connectTimeout <= 0) connectTimeout = DefaultConnectTimeout;
  149. if (readTimeout <= 0) readTimeout = DefaultReadTimeout;
  150. m_conn = mysql_init(nullptr);
  151. MySQLUtil::set_mysql_timeout(m_conn, MySQLUtil::ConnectTimeout,
  152. connectTimeout);
  153. MySQLUtil::set_mysql_timeout(m_conn, MySQLUtil::ReadTimeout, readTimeout);
  154. MYSQL *ret = mysql_real_connect(m_conn, server->getIP().c_str(),
  155. server->getUserName().c_str(),
  156. server->getPassword().c_str(),
  157. server->getDatabase().c_str(),
  158. server->getPort(), nullptr, 0);
  159. if (!ret) {
  160. int code = mysql_errno(m_conn);
  161. const char *msg = mysql_error(m_conn);
  162. string smsg = msg ? msg : "";
  163. mysql_close(m_conn);
  164. m_conn = nullptr;
  165. throw DBConnectionException(code, server->getIP().c_str(),
  166. server->getDatabase().c_str(),
  167. smsg.c_str());
  168. }
  169. // Setting session variables
  170. if (server->getSessionVariables().size()) {
  171. string sessionCmd = string("SET ");
  172. for (SessionVariableVec::const_iterator iter =
  173. server->getSessionVariables().begin(); iter !=
  174. server->getSessionVariables().end(); iter++) {
  175. if (iter != server->getSessionVariables().begin()) {
  176. sessionCmd += ", ";
  177. }
  178. sessionCmd += string("SESSION ") + iter->first + string("=") +
  179. iter->second;
  180. }
  181. char *sessionVarSQL = (char*)Util::safe_malloc(sessionCmd.length() * 2 + 1);
  182. mysql_real_escape_string(m_conn, sessionVarSQL, sessionCmd.c_str(),
  183. sessionCmd.length());
  184. bool failure = mysql_query(m_conn, sessionVarSQL);
  185. Util::safe_free(sessionVarSQL);
  186. if (failure) {
  187. int code = mysql_errno(m_conn);
  188. throw DatabaseException(code, "Failed to execute SQL '%s': %s (%d)",
  189. sessionCmd.c_str(), mysql_error(m_conn), code);
  190. }
  191. }
  192. m_server = server;
  193. m_connectTimeout = connectTimeout;
  194. m_readTimeout = readTimeout;
  195. }
  196. void DBConn::close() {
  197. if (isOpened()) {
  198. mysql_close(m_conn);
  199. m_conn = nullptr;
  200. m_server.reset();
  201. }
  202. }
  203. void DBConn::escapeString(const char *s, std::string &out) {
  204. escapeString(s, strlen(s), out);
  205. }
  206. void DBConn::escapeString(const char *s, int len, std::string &out) {
  207. assert(s);
  208. assert(isOpened());
  209. if (len) {
  210. char *buffer = (char*)malloc(len * 2 + 1);
  211. mysql_real_escape_string(m_conn, buffer, s, len);
  212. out = buffer;
  213. free(buffer);
  214. }
  215. }
  216. int DBConn::execute(const std::string &sql, DBDataSet *ds /* = NULL */,
  217. bool retryQueryOnFail /* = true */) {
  218. return execute(sql.c_str(), ds, retryQueryOnFail);
  219. }
  220. int DBConn::execute(const char *sql, DBDataSet *ds /* = NULL */,
  221. bool retryQueryOnFail /* = true */) {
  222. assert(sql && *sql);
  223. assert(isOpened());
  224. {
  225. bool failure;
  226. if ((failure = mysql_query(m_conn, sql))) {
  227. if (retryQueryOnFail) {
  228. for (int count = 0; count < m_maxRetryOpenOnFail; count++) {
  229. open(m_server, m_connectTimeout, m_readTimeout);
  230. failure = mysql_query(m_conn, sql);
  231. if (!failure) break;
  232. }
  233. }
  234. if (failure) {
  235. int code = mysql_errno(m_conn);
  236. throw DatabaseException(code, "Failed to execute SQL '%s': %s (%d)",
  237. sql, mysql_error(m_conn), code);
  238. }
  239. }
  240. }
  241. MYSQL_RES *result = mysql_store_result(m_conn);
  242. if (!result) {
  243. int code = mysql_errno(m_conn);
  244. if (code) {
  245. throw DatabaseException(code, "Failed to execute SQL '%s': %s (%d)", sql,
  246. mysql_error(m_conn), code);
  247. }
  248. }
  249. int affected = mysql_affected_rows(m_conn);
  250. if (ds) {
  251. ds->addResult(m_conn, result);
  252. } else {
  253. mysql_free_result(result);
  254. }
  255. return affected;
  256. }
  257. int DBConn::getLastInsertId() {
  258. assert(isOpened());
  259. return mysql_insert_id(m_conn);
  260. }
  261. int DBConn::parallelExecute(const char *sql, DBDataSet &ds,
  262. ErrorInfoMap &errors, int maxThread,
  263. bool retryQueryOnFail, int connectTimeout,
  264. int readTimeout,
  265. int maxRetryOpenOnFail,
  266. int maxRetryQueryOnFail) {
  267. assert(sql && *sql);
  268. if (s_localDatabases.empty()) {
  269. return -1;
  270. }
  271. DBConnQueryJobPtrVec jobs;
  272. Mutex mutex;
  273. jobs.reserve(s_localDatabases.size());
  274. string ssql = sql; // so we have copy-on-write in the loop
  275. for (DatabaseMap::const_iterator iter = s_localDatabases.begin();
  276. iter != s_localDatabases.end(); ++iter) {
  277. jobs.push_back(DBConnQueryJobPtr(
  278. new DBConnQueryJob(iter->second, ssql, iter->first,
  279. mutex, ds,
  280. retryQueryOnFail, connectTimeout,
  281. readTimeout,
  282. maxRetryOpenOnFail,
  283. maxRetryQueryOnFail)));
  284. }
  285. return parallelExecute(jobs, errors, maxThread);
  286. }
  287. int DBConn::parallelExecute(const ServerQueryVec &sqls, DBDataSet &ds,
  288. ErrorInfoMap &errors, int maxThread,
  289. bool retryQueryOnFail, int connectTimeout,
  290. int readTimeout,
  291. int maxRetryOpenOnFail,
  292. int maxRetryQueryOnFail) {
  293. if (sqls.empty()) {
  294. return 0;
  295. }
  296. DBConnQueryJobPtrVec jobs;
  297. Mutex mutex;
  298. jobs.reserve(sqls.size());
  299. for (unsigned int i = 0; i < sqls.size(); i++) {
  300. const ServerQuery &query = sqls[i];
  301. DBConnQueryJobPtr job(
  302. new DBConnQueryJob(query.first, query.second, i, mutex, ds,
  303. retryQueryOnFail, connectTimeout,
  304. readTimeout,
  305. maxRetryOpenOnFail, maxRetryQueryOnFail));
  306. jobs.push_back(job);
  307. }
  308. return parallelExecute(jobs, errors, maxThread);
  309. }
  310. int DBConn::parallelExecute(const ServerQueryVec &sqls, DBDataSetPtrVec &dss,
  311. ErrorInfoMap &errors, int maxThread,
  312. bool retryQueryOnFail, int connectTimeout,
  313. int readTimeout,
  314. int maxRetryOpenOnFail,
  315. int maxRetryQueryOnFail) {
  316. assert(sqls.size() == dss.size());
  317. if (sqls.empty()) {
  318. return 0;
  319. }
  320. DBConnQueryJobPtrVec jobs;
  321. Mutex mutex;
  322. jobs.reserve(sqls.size());
  323. for (unsigned int i = 0; i < sqls.size(); i++) {
  324. const ServerQuery &query = sqls[i];
  325. DBConnQueryJobPtr job(
  326. new DBConnQueryJob(query.first, query.second, i, mutex,
  327. *dss[i], retryQueryOnFail, connectTimeout,
  328. readTimeout,
  329. maxRetryOpenOnFail, maxRetryQueryOnFail));
  330. jobs.push_back(job);
  331. }
  332. return parallelExecute(jobs, errors, maxThread);
  333. }
  334. int DBConn::parallelExecute(DBConnQueryJobPtrVec &jobs, ErrorInfoMap &errors,
  335. int maxThread) {
  336. if (maxThread <= 0) maxThread = DefaultWorkerCount;
  337. JobDispatcher<DBConnQueryJob, DBConnQueryWorker>(jobs, maxThread).run();
  338. int affected = 0;
  339. for (unsigned int i = 0; i < jobs.size(); i++) {
  340. DBConnQueryJobPtr job = jobs[i];
  341. int count = job->m_affected;
  342. if (count >= 0) {
  343. affected += count;
  344. } else {
  345. errors[job->m_index] = job->m_error;
  346. }
  347. }
  348. return affected;
  349. }
  350. void DBConnQueryWorker::doJob(DBConnQueryJobPtr job) {
  351. string &sql = job->m_sql;
  352. Util::replaceAll(sql, "INDEX", lexical_cast<string>(job->m_index).c_str());
  353. if (!job->m_server) {
  354. job->m_affected = -1;
  355. job->m_error.code = -1;
  356. job->m_error.msg = "(server info missing)";
  357. return;
  358. }
  359. try {
  360. DBConn conn;
  361. int count = 0;
  362. retry:
  363. try {
  364. count++;
  365. conn.open(job->m_server, job->m_connectTimeout, job->m_readTimeout);
  366. } catch (DatabaseException &e) {
  367. if (job->m_retryQueryOnFail &&
  368. count <= job->m_maxRetryQueryOnFail) {
  369. goto retry;
  370. } else {
  371. throw;
  372. }
  373. }
  374. if (job->m_dsResult) {
  375. DBDataSet ds;
  376. job->m_affected = conn.execute(sql.c_str(), &ds,
  377. job->m_retryQueryOnFail);
  378. Lock lock(*job->m_dsMutex);
  379. job->m_dsResult->addDataSet(ds);
  380. } else {
  381. job->m_affected = conn.execute(sql.c_str(), nullptr,
  382. job->m_retryQueryOnFail);
  383. }
  384. } catch (DatabaseException &e) {
  385. job->m_affected = -1;
  386. job->m_error.code = e.m_code;
  387. job->m_error.msg = e.getMessage();
  388. } catch (Exception &e) {
  389. job->m_affected = -1;
  390. job->m_error.code = -1;
  391. job->m_error.msg = e.getMessage();
  392. } catch (std::exception &e) {
  393. job->m_affected = -1;
  394. job->m_error.code = -1;
  395. job->m_error.msg = e.what();
  396. } catch (...) {
  397. job->m_affected = -1;
  398. job->m_error.code = -1;
  399. job->m_error.msg = "(unknown exception)";
  400. }
  401. }
  402. ///////////////////////////////////////////////////////////////////////////////
  403. }