PageRenderTime 38ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/runtime/ext/ext_mysql.cpp

http://github.com/facebook/hiphop-php
C++ | 1960 lines | 1650 code | 210 blank | 100 comment | 343 complexity | c4d0c7d8f07133f0fe0e3390d1a26840 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. | Copyright (c) 1997-2010 The PHP Group |
  7. +----------------------------------------------------------------------+
  8. | This source file is subject to version 3.01 of the PHP license, |
  9. | that is bundled with this package in the file LICENSE, and is |
  10. | available through the world-wide-web at the following url: |
  11. | http://www.php.net/license/3_01.txt |
  12. | If you did not receive a copy of the PHP license and are unable to |
  13. | obtain it through the world-wide-web, please send a note to |
  14. | license@php.net so we can mail you a copy immediately. |
  15. +----------------------------------------------------------------------+
  16. */
  17. #include "hphp/runtime/ext/ext_mysql.h"
  18. #include "folly/ScopeGuard.h"
  19. #include "hphp/runtime/ext/ext_preg.h"
  20. #include "hphp/runtime/ext/ext_network.h"
  21. #include "hphp/runtime/ext/mysql_stats.h"
  22. #include "hphp/runtime/base/socket.h"
  23. #include "hphp/runtime/base/runtime-option.h"
  24. #include "hphp/runtime/server/server-stats.h"
  25. #include "hphp/runtime/base/request-local.h"
  26. #include "hphp/runtime/base/extended-logger.h"
  27. #include "hphp/util/timer.h"
  28. #include "hphp/util/db-mysql.h"
  29. #include "folly/String.h"
  30. #include <netinet/in.h>
  31. #include <netdb.h>
  32. #include "hphp/system/systemlib.h"
  33. namespace HPHP {
  34. ///////////////////////////////////////////////////////////////////////////////
  35. bool mysqlExtension::ReadOnly = false;
  36. #ifdef FACEBOOK
  37. bool mysqlExtension::Localize = false;
  38. #endif
  39. int mysqlExtension::ConnectTimeout = 1000;
  40. int mysqlExtension::ReadTimeout = 1000;
  41. int mysqlExtension::WaitTimeout = -1;
  42. int mysqlExtension::SlowQueryThreshold = 1000; // ms
  43. bool mysqlExtension::KillOnTimeout = false;
  44. int mysqlExtension::MaxRetryOpenOnFail = 1;
  45. int mysqlExtension::MaxRetryQueryOnFail = 1;
  46. std::string mysqlExtension::Socket = "";
  47. mysqlExtension s_mysql_extension;
  48. ///////////////////////////////////////////////////////////////////////////////
  49. MySQLResult::MySQLResult(MYSQL_RES *res, bool localized /* = false */)
  50. : m_res(res)
  51. , m_current_async_row(nullptr)
  52. , m_localized(localized)
  53. , m_fields(nullptr)
  54. , m_current_field(-1)
  55. , m_field_count(0)
  56. , m_conn(nullptr)
  57. {
  58. if (localized) {
  59. m_res = nullptr; // ensure that localized results don't have another result
  60. m_rows = smart::list<smart::vector<Variant>>(1); // sentinel
  61. m_current_row = m_rows->begin();
  62. m_row_ready = false;
  63. m_row_count = 0;
  64. }
  65. }
  66. MySQLResult::~MySQLResult() {
  67. close();
  68. if (m_fields) {
  69. smart_delete_array(m_fields, m_field_count);
  70. m_fields = nullptr;
  71. }
  72. if (m_conn) {
  73. m_conn->decRefCount();
  74. m_conn = nullptr;
  75. }
  76. }
  77. void MySQLResult::sweep() {
  78. close();
  79. // Note that ~MySQLResult is *not* going to run when we are swept.
  80. }
  81. ///////////////////////////////////////////////////////////////////////////////
  82. class MySQLStaticInitializer {
  83. public:
  84. MySQLStaticInitializer() {
  85. mysql_library_init(0, NULL, NULL);
  86. }
  87. };
  88. static MySQLStaticInitializer s_mysql_initializer;
  89. class MySQLRequestData : public RequestEventHandler {
  90. public:
  91. virtual void requestInit() {
  92. defaultConn.reset();
  93. readTimeout = mysqlExtension::ReadTimeout;
  94. totalRowCount = 0;
  95. }
  96. virtual void requestShutdown() {
  97. defaultConn.reset();
  98. totalRowCount = 0;
  99. }
  100. Resource defaultConn;
  101. int readTimeout;
  102. int totalRowCount; // from all queries in current request
  103. };
  104. IMPLEMENT_STATIC_REQUEST_LOCAL(MySQLRequestData, s_mysql_data);
  105. ///////////////////////////////////////////////////////////////////////////////
  106. // class MySQL statics
  107. int MySQL::s_default_port = 0;
  108. MySQL *MySQL::Get(CVarRef link_identifier) {
  109. if (link_identifier.isNull()) {
  110. return GetDefaultConn();
  111. }
  112. MySQL *mysql = link_identifier.toResource().getTyped<MySQL>
  113. (!RuntimeOption::ThrowBadTypeExceptions,
  114. !RuntimeOption::ThrowBadTypeExceptions);
  115. return mysql;
  116. }
  117. MYSQL *MySQL::GetConn(CVarRef link_identifier, MySQL **rconn /* = NULL */) {
  118. MySQL *mySQL = Get(link_identifier);
  119. MYSQL *ret = nullptr;
  120. if (mySQL) {
  121. ret = mySQL->get();
  122. }
  123. if (ret == nullptr) {
  124. raise_warning("supplied argument is not a valid MySQL-Link resource");
  125. }
  126. // Don't return a connection where mysql_real_connect() failed to most
  127. // f_mysql_* APIs (the ones that deal with errno where we do want to do this
  128. // anyway use MySQL::Get instead) as mysqlclient doesn't support passing
  129. // connections in that state and it can crash.
  130. if (mySQL && mySQL->m_last_error_set) {
  131. ret = nullptr;
  132. } else if (rconn) {
  133. *rconn = mySQL;
  134. }
  135. return ret;
  136. }
  137. bool MySQL::CloseConn(CVarRef link_identifier) {
  138. MySQL *mySQL = Get(link_identifier);
  139. if (mySQL && !mySQL->isPersistent()) {
  140. mySQL->close();
  141. }
  142. return true;
  143. }
  144. int MySQL::GetDefaultPort() {
  145. if (s_default_port <= 0) {
  146. s_default_port = MYSQL_PORT;
  147. char *env = getenv("MYSQL_TCP_PORT");
  148. if (env && *env) {
  149. s_default_port = atoi(env);
  150. } else {
  151. Variant ret = f_getservbyname("mysql", "tcp");
  152. if (!same(ret, false)) {
  153. s_default_port = ret.toInt16();
  154. }
  155. }
  156. }
  157. return s_default_port;
  158. }
  159. String MySQL::GetDefaultSocket() {
  160. if (!mysqlExtension::Socket.empty()) {
  161. return mysqlExtension::Socket;
  162. }
  163. return MYSQL_UNIX_ADDR;
  164. }
  165. String MySQL::GetHash(const String& host, int port, const String& socket, const String& username,
  166. const String& password, int client_flags) {
  167. char buf[1024];
  168. snprintf(buf, sizeof(buf), "%s:%d:%s:%s:%s:%d",
  169. host.data(), port, socket.data(),
  170. username.data(), password.data(), client_flags);
  171. return String(buf, CopyString);
  172. }
  173. MySQL *MySQL::GetCachedImpl(const char *name, const String& host, int port,
  174. const String& socket, const String& username, const String& password,
  175. int client_flags) {
  176. String key = GetHash(host, port, socket, username, password, client_flags);
  177. return dynamic_cast<MySQL*>(g_persistentObjects->get(name, key.data()));
  178. }
  179. void MySQL::SetCachedImpl(const char *name, const String& host, int port,
  180. const String& socket, const String& username, const String& password,
  181. int client_flags, MySQL *conn) {
  182. String key = GetHash(host, port, socket, username, password, client_flags);
  183. g_persistentObjects->set(name, key.data(), conn);
  184. }
  185. MySQL *MySQL::GetDefaultConn() {
  186. return s_mysql_data->defaultConn.getTyped<MySQL>(true);
  187. }
  188. void MySQL::SetDefaultConn(MySQL *conn) {
  189. s_mysql_data->defaultConn = conn;
  190. }
  191. ///////////////////////////////////////////////////////////////////////////////
  192. // class MySQL
  193. static MYSQL *configure_conn(MYSQL* conn) {
  194. mysql_options(conn, MYSQL_OPT_LOCAL_INFILE, 0);
  195. if (mysqlExtension::ConnectTimeout) {
  196. MySQLUtil::set_mysql_timeout(conn, MySQLUtil::ConnectTimeout,
  197. mysqlExtension::ConnectTimeout);
  198. }
  199. int readTimeout = s_mysql_data->readTimeout;
  200. if (readTimeout) {
  201. MySQLUtil::set_mysql_timeout(conn, MySQLUtil::ReadTimeout, readTimeout);
  202. MySQLUtil::set_mysql_timeout(conn, MySQLUtil::WriteTimeout, readTimeout);
  203. }
  204. return conn;
  205. }
  206. static MYSQL *create_new_conn() {
  207. return configure_conn(mysql_init(nullptr));
  208. }
  209. MySQL::MySQL(const char *host, int port, const char *username,
  210. const char *password, const char *database,
  211. MYSQL* raw_connection)
  212. : m_port(port), m_last_error_set(false), m_last_errno(0),
  213. m_xaction_count(0), m_multi_query(false) {
  214. if (host) m_host = host;
  215. if (username) m_username = username;
  216. if (password) m_password = password;
  217. if (database) m_database = database;
  218. if (raw_connection) {
  219. m_conn = configure_conn(raw_connection);
  220. } else {
  221. m_conn = create_new_conn();
  222. }
  223. }
  224. MySQL::~MySQL() {
  225. close();
  226. }
  227. void MySQL::sweep() {
  228. // may or may not be smart allocated
  229. delete this;
  230. }
  231. void MySQL::setLastError(const char *func) {
  232. assert(m_conn);
  233. m_last_error_set = true;
  234. m_last_errno = mysql_errno(m_conn);
  235. const char *error = mysql_error(m_conn);
  236. m_last_error = error ? error : "";
  237. raise_warning("%s(): %s", func, m_last_error.c_str());
  238. }
  239. void MySQL::close() {
  240. if (!m_conn) {
  241. return;
  242. }
  243. m_last_error_set = false;
  244. m_last_errno = 0;
  245. m_xaction_count = 0;
  246. m_last_error.clear();
  247. mysql_close(m_conn);
  248. m_conn = nullptr;
  249. }
  250. bool MySQL::connect(const String& host, int port, const String& socket, const String& username,
  251. const String& password, const String& database,
  252. int client_flags, int connect_timeout) {
  253. if (m_conn == NULL) {
  254. m_conn = create_new_conn();
  255. }
  256. if (connect_timeout >= 0) {
  257. MySQLUtil::set_mysql_timeout(m_conn, MySQLUtil::ConnectTimeout,
  258. connect_timeout);
  259. }
  260. if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLStats) {
  261. ServerStats::Log("sql.conn", 1);
  262. }
  263. IOStatusHelper io("mysql::connect", host.data(), port);
  264. m_xaction_count = 0;
  265. bool ret = mysql_real_connect(m_conn, host.data(), username.data(),
  266. password.data(),
  267. (database.empty() ? NULL : database.data()),
  268. port,
  269. socket.empty() ? NULL : socket.data(),
  270. client_flags);
  271. if (ret && mysqlExtension::WaitTimeout > 0) {
  272. String query("set session wait_timeout=");
  273. query += String((int64_t)(mysqlExtension::WaitTimeout / 1000));
  274. if (mysql_real_query(m_conn, query.data(), query.size())) {
  275. raise_notice("MySQL::connect: failed setting session wait timeout: %s",
  276. mysql_error(m_conn));
  277. }
  278. }
  279. return ret;
  280. }
  281. bool MySQL::reconnect(const String& host, int port, const String& socket, const String& username,
  282. const String& password, const String& database,
  283. int client_flags, int connect_timeout) {
  284. if (m_conn == NULL) {
  285. m_conn = create_new_conn();
  286. if (connect_timeout >= 0) {
  287. MySQLUtil::set_mysql_timeout(m_conn, MySQLUtil::ConnectTimeout,
  288. connect_timeout);
  289. }
  290. if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLStats) {
  291. ServerStats::Log("sql.reconn_new", 1);
  292. }
  293. IOStatusHelper io("mysql::connect", host.data(), port);
  294. return mysql_real_connect(m_conn, host.data(), username.data(),
  295. password.data(),
  296. (database.empty() ? NULL : database.data()),
  297. port, socket.data(), client_flags);
  298. }
  299. if (!mysql_ping(m_conn)) {
  300. if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLStats) {
  301. ServerStats::Log("sql.reconn_ok", 1);
  302. }
  303. if (!database.empty()) {
  304. mysql_select_db(m_conn, database.data());
  305. }
  306. return true;
  307. }
  308. if (connect_timeout >= 0) {
  309. MySQLUtil::set_mysql_timeout(m_conn, MySQLUtil::ConnectTimeout,
  310. connect_timeout);
  311. }
  312. if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLStats) {
  313. ServerStats::Log("sql.reconn_old", 1);
  314. }
  315. IOStatusHelper io("mysql::connect", host.data(), port);
  316. m_xaction_count = 0;
  317. return mysql_real_connect(m_conn, host.data(), username.data(),
  318. password.data(),
  319. (database.empty() ? NULL : database.data()),
  320. port, socket.data(), client_flags);
  321. }
  322. ///////////////////////////////////////////////////////////////////////////////
  323. // helpers
  324. static MySQLResult *get_result(CVarRef result) {
  325. MySQLResult *res = result.toResource().getTyped<MySQLResult>
  326. (!RuntimeOption::ThrowBadTypeExceptions,
  327. !RuntimeOption::ThrowBadTypeExceptions);
  328. if (res == NULL || (res->get() == NULL && !res->isLocalized())) {
  329. raise_warning("supplied argument is not a valid MySQL result resource");
  330. }
  331. return res;
  332. }
  333. static const char *php_mysql_get_field_name(int field_type) {
  334. switch (field_type) {
  335. case FIELD_TYPE_STRING:
  336. case FIELD_TYPE_VAR_STRING:
  337. return "string";
  338. case FIELD_TYPE_TINY:
  339. case FIELD_TYPE_SHORT:
  340. case FIELD_TYPE_LONG:
  341. case FIELD_TYPE_LONGLONG:
  342. case FIELD_TYPE_INT24:
  343. return "int";
  344. case FIELD_TYPE_FLOAT:
  345. case FIELD_TYPE_DOUBLE:
  346. case FIELD_TYPE_DECIMAL:
  347. //case FIELD_TYPE_NEWDECIMAL:
  348. return "real";
  349. case FIELD_TYPE_TIMESTAMP:
  350. return "timestamp";
  351. case FIELD_TYPE_YEAR:
  352. return "year";
  353. case FIELD_TYPE_DATE:
  354. case FIELD_TYPE_NEWDATE:
  355. return "date";
  356. case FIELD_TYPE_TIME:
  357. return "time";
  358. case FIELD_TYPE_SET:
  359. return "set";
  360. case FIELD_TYPE_ENUM:
  361. return "enum";
  362. case FIELD_TYPE_GEOMETRY:
  363. return "geometry";
  364. case FIELD_TYPE_DATETIME:
  365. return "datetime";
  366. case FIELD_TYPE_TINY_BLOB:
  367. case FIELD_TYPE_MEDIUM_BLOB:
  368. case FIELD_TYPE_LONG_BLOB:
  369. case FIELD_TYPE_BLOB:
  370. return "blob";
  371. case FIELD_TYPE_NULL:
  372. return "null";
  373. default:
  374. break;
  375. }
  376. return "unknown";
  377. }
  378. #define PHP_MYSQL_FIELD_NAME 1
  379. #define PHP_MYSQL_FIELD_TABLE 2
  380. #define PHP_MYSQL_FIELD_LEN 3
  381. #define PHP_MYSQL_FIELD_TYPE 4
  382. #define PHP_MYSQL_FIELD_FLAGS 5
  383. static Variant php_mysql_field_info(CVarRef result, int field,
  384. int entry_type) {
  385. MySQLResult *res = get_result(result);
  386. if (res == NULL) return false;
  387. if (!res->seekField(field)) return false;
  388. MySQLFieldInfo *info;
  389. if (!(info = res->fetchFieldInfo())) return false;
  390. switch (entry_type) {
  391. case PHP_MYSQL_FIELD_NAME:
  392. return info->name;
  393. case PHP_MYSQL_FIELD_TABLE:
  394. return info->table;
  395. case PHP_MYSQL_FIELD_LEN:
  396. return info->length;
  397. case PHP_MYSQL_FIELD_TYPE:
  398. return php_mysql_get_field_name(info->type);
  399. case PHP_MYSQL_FIELD_FLAGS:
  400. {
  401. char buf[512];
  402. buf[0] = '\0';
  403. unsigned int flags = info->flags;
  404. #ifdef IS_NOT_NULL
  405. if (IS_NOT_NULL(flags)) {
  406. strcat(buf, "not_null ");
  407. }
  408. #endif
  409. #ifdef IS_PRI_KEY
  410. if (IS_PRI_KEY(flags)) {
  411. strcat(buf, "primary_key ");
  412. }
  413. #endif
  414. #ifdef UNIQUE_KEY_FLAG
  415. if (flags & UNIQUE_KEY_FLAG) {
  416. strcat(buf, "unique_key ");
  417. }
  418. #endif
  419. #ifdef MULTIPLE_KEY_FLAG
  420. if (flags & MULTIPLE_KEY_FLAG) {
  421. strcat(buf, "multiple_key ");
  422. }
  423. #endif
  424. #ifdef IS_BLOB
  425. if (IS_BLOB(flags)) {
  426. strcat(buf, "blob ");
  427. }
  428. #endif
  429. #ifdef UNSIGNED_FLAG
  430. if (flags & UNSIGNED_FLAG) {
  431. strcat(buf, "unsigned ");
  432. }
  433. #endif
  434. #ifdef ZEROFILL_FLAG
  435. if (flags & ZEROFILL_FLAG) {
  436. strcat(buf, "zerofill ");
  437. }
  438. #endif
  439. #ifdef BINARY_FLAG
  440. if (flags & BINARY_FLAG) {
  441. strcat(buf, "binary ");
  442. }
  443. #endif
  444. #ifdef ENUM_FLAG
  445. if (flags & ENUM_FLAG) {
  446. strcat(buf, "enum ");
  447. }
  448. #endif
  449. #ifdef SET_FLAG
  450. if (flags & SET_FLAG) {
  451. strcat(buf, "set ");
  452. }
  453. #endif
  454. #ifdef AUTO_INCREMENT_FLAG
  455. if (flags & AUTO_INCREMENT_FLAG) {
  456. strcat(buf, "auto_increment ");
  457. }
  458. #endif
  459. #ifdef TIMESTAMP_FLAG
  460. if (flags & TIMESTAMP_FLAG) {
  461. strcat(buf, "timestamp ");
  462. }
  463. #endif
  464. int len = strlen(buf);
  465. /* remove trailing space, if present */
  466. if (len && buf[len-1] == ' ') {
  467. buf[len-1] = 0;
  468. len--;
  469. }
  470. return String(buf, len, CopyString);
  471. }
  472. default:
  473. break;
  474. }
  475. return false;
  476. }
  477. static Variant php_mysql_do_connect(String server, String username,
  478. String password, String database,
  479. int client_flags, bool persistent,
  480. bool async,
  481. int connect_timeout_ms,
  482. int query_timeout_ms) {
  483. if (connect_timeout_ms < 0) {
  484. connect_timeout_ms = mysqlExtension::ConnectTimeout;
  485. }
  486. if (query_timeout_ms < 0) {
  487. query_timeout_ms = s_mysql_data->readTimeout;
  488. }
  489. if (server.empty()) server = MySQL::GetDefaultServer();
  490. if (username.empty()) username = MySQL::GetDefaultUsername();
  491. if (password.empty()) password = MySQL::GetDefaultPassword();
  492. if (database.empty()) database = MySQL::GetDefaultDatabase();
  493. // server format: hostname[:port][:/path/to/socket]
  494. String host, socket;
  495. int port = MYSQL_PORT;
  496. int pos = server.find(':');
  497. if (pos >= 0) {
  498. host = server.substr(0, pos);
  499. if (server.charAt(pos + 1) != '/') {
  500. String sport = server.substr(pos + 1);
  501. port = sport.toInt32();
  502. pos = sport.find(':');
  503. if (pos >= 0) {
  504. socket = sport.substr(pos + 1);
  505. }
  506. } else {
  507. socket = server.substr(pos + 1);
  508. }
  509. } else {
  510. host = server;
  511. port = MySQL::GetDefaultPort();
  512. }
  513. if (socket.empty()) {
  514. socket = MySQL::GetDefaultSocket();
  515. }
  516. Resource ret;
  517. MySQL *mySQL = NULL;
  518. if (persistent) {
  519. mySQL = MySQL::GetPersistent(host, port, socket, username, password,
  520. client_flags);
  521. }
  522. if (mySQL == NULL) {
  523. mySQL = new MySQL(host.c_str(), port, username.c_str(), password.c_str(),
  524. database.c_str());
  525. ret = mySQL;
  526. if (async) {
  527. #ifdef FACEBOOK
  528. if (!mySQL->async_connect(host, port, socket, username, password,
  529. database)) {
  530. MySQL::SetDefaultConn(mySQL); // so we can report errno by mysql_errno()
  531. mySQL->setLastError("mysql_real_connect_nonblocking_init");
  532. return false;
  533. }
  534. #else
  535. throw NotImplementedException("mysql_async_connect_start");
  536. #endif
  537. } else {
  538. if (!mySQL->connect(host, port, socket, username, password,
  539. database, client_flags, connect_timeout_ms)) {
  540. MySQL::SetDefaultConn(mySQL); // so we can report errno by mysql_errno()
  541. mySQL->setLastError("mysql_connect");
  542. return false;
  543. }
  544. }
  545. } else {
  546. ret = mySQL;
  547. if (!mySQL->reconnect(host, port, socket, username, password,
  548. database, client_flags, connect_timeout_ms)) {
  549. MySQL::SetDefaultConn(mySQL); // so we can report errno by mysql_errno()
  550. mySQL->setLastError("mysql_connect");
  551. return false;
  552. }
  553. }
  554. if (persistent) {
  555. MySQL::SetPersistent(host, port, socket, username, password,
  556. client_flags, mySQL);
  557. }
  558. MySQL::SetDefaultConn(mySQL);
  559. return ret;
  560. }
  561. ///////////////////////////////////////////////////////////////////////////////
  562. Variant f_mysql_connect(const String& server /* = null_string */,
  563. const String& username /* = null_string */,
  564. const String& password /* = null_string */,
  565. bool new_link /* = false */,
  566. int client_flags /* = 0 */,
  567. int connect_timeout_ms /* = -1 */,
  568. int query_timeout_ms /* = -1 */) {
  569. return php_mysql_do_connect(server, username, password, "",
  570. client_flags, false, false,
  571. connect_timeout_ms, query_timeout_ms);
  572. }
  573. Variant f_mysql_connect_with_db(const String& server /* = null_string */,
  574. const String& username /* = null_string */,
  575. const String& password /* = null_string */,
  576. const String& database /* = null_string */,
  577. bool new_link /* = false */,
  578. int client_flags /* = 0 */,
  579. int connect_timeout_ms /* = -1 */,
  580. int query_timeout_ms /* = -1 */) {
  581. return php_mysql_do_connect(server, username, password, database,
  582. client_flags, false, false,
  583. connect_timeout_ms, query_timeout_ms);
  584. }
  585. Variant f_mysql_pconnect(const String& server /* = null_string */,
  586. const String& username /* = null_string */,
  587. const String& password /* = null_string */,
  588. int client_flags /* = 0 */,
  589. int connect_timeout_ms /* = -1 */,
  590. int query_timeout_ms /* = -1 */) {
  591. return php_mysql_do_connect(server, username, password, "",
  592. client_flags, true, false,
  593. connect_timeout_ms, query_timeout_ms);
  594. }
  595. Variant f_mysql_pconnect_with_db(const String& server /* = null_string */,
  596. const String& username /* = null_string */,
  597. const String& password /* = null_string */,
  598. const String& database /* = null_string */,
  599. int client_flags /* = 0 */,
  600. int connect_timeout_ms /* = -1 */,
  601. int query_timeout_ms /* = -1 */) {
  602. return php_mysql_do_connect(server, username, password, database,
  603. client_flags, true, false,
  604. connect_timeout_ms, query_timeout_ms);
  605. }
  606. bool f_mysql_set_timeout(int query_timeout_ms /* = -1 */,
  607. CVarRef link_identifier /* = null */) {
  608. if (query_timeout_ms < 0) {
  609. query_timeout_ms = mysqlExtension::ReadTimeout;
  610. }
  611. s_mysql_data->readTimeout = query_timeout_ms;
  612. return true;
  613. }
  614. String f_mysql_escape_string(const String& unescaped_string) {
  615. char *new_str = (char *)malloc(unescaped_string.size() * 2 + 1);
  616. int new_len = mysql_escape_string(new_str, unescaped_string.data(),
  617. unescaped_string.size());
  618. return String(new_str, new_len, AttachString);
  619. }
  620. Variant f_mysql_real_escape_string(const String& unescaped_string,
  621. CVarRef link_identifier /* = null */) {
  622. MYSQL *conn = MySQL::GetConn(link_identifier);
  623. if (conn) {
  624. char *new_str = (char *)malloc(unescaped_string.size() * 2 + 1);
  625. int new_len = mysql_real_escape_string(conn, new_str,
  626. unescaped_string.data(),
  627. unescaped_string.size());
  628. return String(new_str, new_len, AttachString);
  629. }
  630. return false;
  631. }
  632. String f_mysql_get_client_info() {
  633. return String(mysql_get_client_info(), CopyString);
  634. }
  635. Variant f_mysql_set_charset(const String& charset,
  636. CVarRef link_identifier /* = uninit_null() */) {
  637. MYSQL *conn = MySQL::GetConn(link_identifier);
  638. if (!conn) return uninit_null();
  639. return !mysql_set_character_set(conn, charset.data());
  640. }
  641. Variant f_mysql_ping(CVarRef link_identifier /* = uninit_null() */) {
  642. MYSQL *conn = MySQL::GetConn(link_identifier);
  643. if (!conn) return uninit_null();
  644. return !mysql_ping(conn);
  645. }
  646. Variant f_mysql_client_encoding(CVarRef link_identifier /* = uninit_null() */) {
  647. MYSQL *conn = MySQL::GetConn(link_identifier);
  648. if (!conn) return false;
  649. return String(mysql_character_set_name(conn), CopyString);
  650. }
  651. Variant f_mysql_close(CVarRef link_identifier /* = uninit_null() */) {
  652. return MySQL::CloseConn(link_identifier);
  653. }
  654. Variant f_mysql_errno(CVarRef link_identifier /* = null */) {
  655. MySQL *mySQL = MySQL::Get(link_identifier);
  656. if (!mySQL) {
  657. raise_warning("supplied argument is not a valid MySQL-Link resource");
  658. return false;
  659. }
  660. MYSQL *conn = mySQL->get();
  661. if (conn) {
  662. return (int64_t)mysql_errno(conn);
  663. }
  664. if (mySQL->m_last_error_set) {
  665. return (int64_t)mySQL->m_last_errno;
  666. }
  667. return false;
  668. }
  669. Variant f_mysql_error(CVarRef link_identifier /* = null */) {
  670. MySQL *mySQL = MySQL::Get(link_identifier);
  671. if (!mySQL) {
  672. raise_warning("supplied argument is not a valid MySQL-Link resource");
  673. return false;
  674. }
  675. MYSQL *conn = mySQL->get();
  676. if (conn) {
  677. return String(mysql_error(conn), CopyString);
  678. }
  679. if (mySQL->m_last_error_set) {
  680. return String(mySQL->m_last_error);
  681. }
  682. return false;
  683. }
  684. Variant f_mysql_warning_count(CVarRef link_identifier /* = null */) {
  685. MySQL *mySQL = MySQL::Get(link_identifier);
  686. if (!mySQL) {
  687. raise_warning("supplied argument is not a valid MySQL-Link resource");
  688. return false;
  689. }
  690. MYSQL *conn = mySQL->get();
  691. if (conn) {
  692. return (int64_t)mysql_warning_count(conn);
  693. }
  694. return false;
  695. }
  696. Variant f_mysql_get_host_info(CVarRef link_identifier /* = uninit_null() */) {
  697. MYSQL *conn = MySQL::GetConn(link_identifier);
  698. if (!conn) return false;
  699. return String(mysql_get_host_info(conn), CopyString);
  700. }
  701. Variant f_mysql_get_proto_info(CVarRef link_identifier /* = uninit_null() */) {
  702. MYSQL *conn = MySQL::GetConn(link_identifier);
  703. if (!conn) return false;
  704. return (int64_t)mysql_get_proto_info(conn);
  705. }
  706. Variant f_mysql_get_server_info(CVarRef link_identifier /* = uninit_null() */) {
  707. MYSQL *conn = MySQL::GetConn(link_identifier);
  708. if (!conn) return false;
  709. return String(mysql_get_server_info(conn), CopyString);
  710. }
  711. Variant f_mysql_info(CVarRef link_identifier /* = uninit_null() */) {
  712. MYSQL *conn = MySQL::GetConn(link_identifier);
  713. if (!conn) return false;
  714. return String(mysql_info(conn), CopyString);
  715. }
  716. Variant f_mysql_insert_id(CVarRef link_identifier /* = uninit_null() */) {
  717. MYSQL *conn = MySQL::GetConn(link_identifier);
  718. if (!conn) return false;
  719. return static_cast<int64_t>(mysql_insert_id(conn));
  720. }
  721. Variant f_mysql_stat(CVarRef link_identifier /* = uninit_null() */) {
  722. MYSQL *conn = MySQL::GetConn(link_identifier);
  723. if (!conn) return false;
  724. return String(mysql_stat(conn), CopyString);
  725. }
  726. Variant f_mysql_thread_id(CVarRef link_identifier /* = uninit_null() */) {
  727. MYSQL *conn = MySQL::GetConn(link_identifier);
  728. if (!conn) return false;
  729. return (int64_t)mysql_thread_id(conn);
  730. }
  731. Variant f_mysql_create_db(const String& db,
  732. CVarRef link_identifier /* = uninit_null() */) {
  733. throw NotSupportedException
  734. (__func__, "Deprecated. Use mysql_query(CREATE DATABASE) instead.");
  735. }
  736. Variant f_mysql_select_db(const String& db,
  737. CVarRef link_identifier /* = uninit_null() */) {
  738. MYSQL *conn = MySQL::GetConn(link_identifier);
  739. if (!conn) return false;
  740. return mysql_select_db(conn, db.data()) == 0;
  741. }
  742. Variant f_mysql_drop_db(const String& db,
  743. CVarRef link_identifier /* = uninit_null() */) {
  744. throw NotSupportedException
  745. (__func__, "Deprecated. Use mysql_query(DROP DATABASE) instead.");
  746. }
  747. Variant f_mysql_affected_rows(CVarRef link_identifier /* = uninit_null() */) {
  748. MYSQL *conn = MySQL::GetConn(link_identifier);
  749. if (!conn) return false;
  750. return static_cast<int64_t>(mysql_affected_rows(conn));
  751. }
  752. ///////////////////////////////////////////////////////////////////////////////
  753. // query functions
  754. // Zend returns strings and NULL only, not integers or floats. We
  755. // return ints (and, sometimes, actual doubles). TODO: make this
  756. // consistent or a runtime parameter or something.
  757. Variant mysql_makevalue(const String& data, MYSQL_FIELD *mysql_field) {
  758. return mysql_makevalue(data, mysql_field->type);
  759. }
  760. Variant mysql_makevalue(const String& data, enum_field_types field_type) {
  761. switch (field_type) {
  762. case MYSQL_TYPE_DECIMAL:
  763. case MYSQL_TYPE_TINY:
  764. case MYSQL_TYPE_SHORT:
  765. case MYSQL_TYPE_LONG:
  766. case MYSQL_TYPE_LONGLONG:
  767. case MYSQL_TYPE_INT24:
  768. case MYSQL_TYPE_YEAR:
  769. return data.toInt64();
  770. case MYSQL_TYPE_FLOAT:
  771. case MYSQL_TYPE_DOUBLE:
  772. //case MYSQL_TYPE_NEWDECIMAL:
  773. return data.toDouble();
  774. case MYSQL_TYPE_NULL:
  775. return uninit_null();
  776. default:
  777. break;
  778. }
  779. return data;
  780. }
  781. #ifdef FACEBOOK
  782. extern "C" {
  783. struct MEM_ROOT;
  784. unsigned long cli_safe_read(MYSQL *);
  785. unsigned long net_field_length(unsigned char **);
  786. void free_root(::MEM_ROOT *, int);
  787. }
  788. static bool php_mysql_read_rows(MYSQL *mysql, CVarRef result) {
  789. unsigned long pkt_len;
  790. unsigned char *cp;
  791. unsigned int fields = mysql->field_count;
  792. NET *net = &mysql->net;
  793. MySQLResult *res = get_result(result);
  794. if ((pkt_len = cli_safe_read(mysql)) == packet_error) {
  795. return false;
  796. }
  797. res->setFieldCount((int64_t)fields);
  798. // localizes all the rows
  799. while (*(cp = net->read_pos) != 254 || pkt_len >= 8) {
  800. res->addRow();
  801. for (unsigned int i = 0; i < fields; i++) {
  802. unsigned long len = net_field_length(&cp);
  803. Variant data;
  804. if (len != NULL_LENGTH) {
  805. data = mysql_makevalue(String((char *)cp, len, CopyString),
  806. mysql->fields + i);
  807. cp += len;
  808. if (mysql->fields) {
  809. if (mysql->fields[i].max_length < len)
  810. mysql->fields[i].max_length = len;
  811. }
  812. }
  813. res->addField(std::move(data));
  814. }
  815. if ((pkt_len = cli_safe_read(mysql)) == packet_error) {
  816. return false;
  817. }
  818. }
  819. // localizes all the field info
  820. for (unsigned int i = 0; i < fields; i++) {
  821. res->setFieldInfo((int64_t)i, mysql->fields + i);
  822. }
  823. return true;
  824. }
  825. static Variant php_mysql_localize_result(MYSQL *mysql) {
  826. #if MYSQL_VERSION_ID <= 50138
  827. mysql = mysql->last_used_con;
  828. #endif
  829. if (!mysql->fields) return true;
  830. if (mysql->status != MYSQL_STATUS_GET_RESULT) {
  831. // consistent with php_mysql_do_query_general
  832. return true;
  833. }
  834. mysql->status = MYSQL_STATUS_READY;
  835. Variant result = Resource(NEWOBJ(MySQLResult)(nullptr, true));
  836. if (!php_mysql_read_rows(mysql, result)) {
  837. return false;
  838. }
  839. // clean up
  840. if (mysql->fields) {
  841. free_root(&mysql->field_alloc, 0);
  842. }
  843. mysql->unbuffered_fetch_owner = 0;
  844. return result;
  845. }
  846. #endif // FACEBOOK
  847. static Variant php_mysql_do_query_general(const String& query, CVarRef link_id,
  848. bool use_store, bool async_mode) {
  849. if (mysqlExtension::ReadOnly &&
  850. same(f_preg_match("/^((\\/\\*.*?\\*\\/)|\\(|\\s)*select/i", query), 0)) {
  851. raise_notice("runtime/ext_mysql: write query not executed [%s]",
  852. query.data());
  853. return true; // pretend it worked
  854. }
  855. MySQL *rconn = NULL;
  856. MYSQL *conn = MySQL::GetConn(link_id, &rconn);
  857. if (!conn || !rconn) return false;
  858. if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLStats) {
  859. ServerStats::Log("sql.query", 1);
  860. // removing comments, which can be wrong actually if some string field's
  861. // value has /* or */ in it.
  862. String q = f_preg_replace("/\\/\\*.*?\\*\\//", " ", query).toString();
  863. Variant matches;
  864. f_preg_match("/^(?:\\(|\\s)*(?:"
  865. "(insert).*?\\s+(?:into\\s+)?([^\\s\\(,]+)|"
  866. "(update|set|show)\\s+([^\\s\\(,]+)|"
  867. "(replace).*?\\s+into\\s+([^\\s\\(,]+)|"
  868. "(delete).*?\\s+from\\s+([^\\s\\(,]+)|"
  869. "(select).*?[\\s`]+from\\s+([^\\s\\(,]+))/is",
  870. q, ref(matches));
  871. int size = matches.toArray().size();
  872. if (size > 2) {
  873. string verb = Util::toLower(matches[size - 2].toString().data());
  874. string table = Util::toLower(matches[size - 1].toString().data());
  875. if (!table.empty() && table[0] == '`') {
  876. table = table.substr(1, table.length() - 2);
  877. }
  878. ServerStats::Log(string("sql.query.") + table + "." + verb, 1);
  879. if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLTableStats) {
  880. MySqlStats::Record(verb, rconn->m_xaction_count, table);
  881. if (verb == "update") {
  882. f_preg_match("([^\\s,]+)\\s*=\\s*([^\\s,]+)[\\+\\-]",
  883. q, ref(matches));
  884. size = matches.toArray().size();
  885. if (size > 2 && same(matches[1], matches[2])) {
  886. MySqlStats::Record("incdec", rconn->m_xaction_count, table);
  887. }
  888. }
  889. // we only bump it up when we're in the middle of a transaction
  890. if (rconn->m_xaction_count) {
  891. ++rconn->m_xaction_count;
  892. }
  893. }
  894. } else {
  895. f_preg_match("/^(?:(?:\\/\\*.*?\\*\\/)|\\(|\\s)*"
  896. "(begin|commit|rollback)/is",
  897. query, ref(matches));
  898. size = matches.toArray().size();
  899. if (size == 2) {
  900. string verb = Util::toLower(matches[1].toString().data());
  901. rconn->m_xaction_count = ((verb == "begin") ? 1 : 0);
  902. ServerStats::Log(string("sql.query.") + verb, 1);
  903. if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLTableStats) {
  904. MySqlStats::Record(verb);
  905. }
  906. } else {
  907. raise_warning("Unable to record MySQL stats with: %s", query.data());
  908. ServerStats::Log("sql.query.unknown", 1);
  909. }
  910. }
  911. }
  912. SlowTimer timer(mysqlExtension::SlowQueryThreshold,
  913. "runtime/ext_mysql: slow query", query.data());
  914. IOStatusHelper io("mysql::query", rconn->m_host.c_str(), rconn->m_port);
  915. unsigned long tid = mysql_thread_id(conn);
  916. // disable explicitly
  917. MySQL *mySQL = MySQL::Get(link_id);
  918. if (mySQL->m_multi_query && !mysql_set_server_option(conn, MYSQL_OPTION_MULTI_STATEMENTS_OFF)) {
  919. mySQL->m_multi_query = false;
  920. }
  921. if (async_mode) {
  922. #ifdef FACEBOOK
  923. int ok =
  924. mysql_real_query_nonblocking_init(conn, query.data(), query.size());
  925. if (!ok) {
  926. raise_notice("runtime/ext_mysql: failed async executing [%s] [%s]",
  927. query.data(), mysql_error(conn));
  928. }
  929. return ok;
  930. #else
  931. throw NotImplementedException("mysql_async_query_start");
  932. #endif
  933. }
  934. if (mysql_real_query(conn, query.data(), query.size())) {
  935. #ifdef HHVM_MYSQL_TRACE_MODE
  936. raise_notice("runtime/ext_mysql: failed executing [%s] [%s]", query.data(),
  937. mysql_error(conn));
  938. #endif
  939. // When we are timed out, and we're SELECT-ing, we're potentially
  940. // running a long query on the server without waiting for any results
  941. // back, wasting server resource. So we're sending a KILL command
  942. // to see if we can stop the query execution.
  943. if (tid && mysqlExtension::KillOnTimeout) {
  944. unsigned int errcode = mysql_errno(conn);
  945. if (errcode == 2058 /* CR_NET_READ_INTERRUPTED */ ||
  946. errcode == 2059 /* CR_NET_WRITE_INTERRUPTED */) {
  947. Variant ret = f_preg_match("/^((\\/\\*.*?\\*\\/)|\\(|\\s)*select/is",
  948. query);
  949. if (!same(ret, false)) {
  950. MYSQL *new_conn = create_new_conn();
  951. IOStatusHelper io("mysql::kill", rconn->m_host.c_str(),
  952. rconn->m_port);
  953. MYSQL *connected = mysql_real_connect
  954. (new_conn, rconn->m_host.c_str(), rconn->m_username.c_str(),
  955. rconn->m_password.c_str(), NULL, rconn->m_port, NULL, 0);
  956. if (connected) {
  957. string killsql = "KILL " + boost::lexical_cast<string>(tid);
  958. if (mysql_real_query(connected, killsql.c_str(), killsql.size())) {
  959. raise_warning("Unable to kill thread %lu", tid);
  960. }
  961. }
  962. mysql_close(new_conn);
  963. }
  964. }
  965. }
  966. return false;
  967. }
  968. Logger::Verbose("runtime/ext_mysql: successfully executed [%dms] [%s]",
  969. (int)timer.getTime(), query.data());
  970. MYSQL_RES *mysql_result;
  971. if (use_store) {
  972. #ifdef FACEBOOK
  973. // Facebook specific optimization which depends
  974. // on versions of MySQL which allow access to the
  975. // grotty internals of libmysqlclient
  976. //
  977. // If php_mysql_localize_result ever gets rewritten
  978. // to use standard APIs, this can be opened up to everyone.
  979. if (mysqlExtension::Localize) {
  980. return php_mysql_localize_result(conn);
  981. }
  982. #endif
  983. mysql_result = mysql_store_result(conn);
  984. } else {
  985. mysql_result = mysql_use_result(conn);
  986. }
  987. if (!mysql_result) {
  988. if (mysql_field_count(conn) > 0) {
  989. raise_warning("Unable to save result set");
  990. return false;
  991. }
  992. return true;
  993. }
  994. MySQLResult *r = NEWOBJ(MySQLResult)(mysql_result);
  995. Resource ret(r);
  996. if (RuntimeOption::MaxSQLRowCount > 0 &&
  997. (s_mysql_data->totalRowCount += r->getRowCount())
  998. > RuntimeOption::MaxSQLRowCount) {
  999. ExtendedLogger::Error
  1000. ("MaxSQLRowCount is over: fetching at least %d rows: %s",
  1001. s_mysql_data->totalRowCount, query.data());
  1002. s_mysql_data->totalRowCount = 0; // so no repetitive logging
  1003. }
  1004. return ret;
  1005. }
  1006. Variant f_mysql_query(const String& query, CVarRef link_identifier /* = null */) {
  1007. return php_mysql_do_query_general(query, link_identifier, true, false);
  1008. }
  1009. Variant f_mysql_multi_query(const String& query, CVarRef link_identifier /* = null */) {
  1010. MYSQL *conn = MySQL::GetConn(link_identifier);
  1011. MySQL *mySQL = MySQL::Get(link_identifier);
  1012. if (!mySQL->m_multi_query && !mysql_set_server_option(conn, MYSQL_OPTION_MULTI_STATEMENTS_ON)) {
  1013. mySQL->m_multi_query = true;
  1014. }
  1015. if (mysql_real_query(conn, query.data(), query.size())) {
  1016. #ifdef HHVM_MYSQL_TRACE_MODE
  1017. raise_notice("runtime/ext_mysql: failed executing [%s] [%s]", query.data(),
  1018. mysql_error(conn));
  1019. #endif
  1020. // turning this off clears the errors
  1021. if (!mysql_set_server_option(conn, MYSQL_OPTION_MULTI_STATEMENTS_OFF)) {
  1022. mySQL->m_multi_query = false;
  1023. }
  1024. return false;
  1025. }
  1026. return true;
  1027. }
  1028. int f_mysql_next_result(CVarRef link_identifier /* = null */) {
  1029. MYSQL *conn = MySQL::GetConn(link_identifier);
  1030. if (!mysql_more_results(conn)) {
  1031. raise_strict_warning("There is no next result set. "
  1032. "Please, call mysql_more_results() to check "
  1033. "whether to call this function/method");
  1034. }
  1035. return mysql_next_result(conn);
  1036. }
  1037. bool f_mysql_more_results(CVarRef link_identifier /* = null */) {
  1038. MYSQL *conn = MySQL::GetConn(link_identifier);
  1039. return mysql_more_results(conn);
  1040. }
  1041. Variant f_mysql_fetch_result(CVarRef link_identifier /* = null */) {
  1042. MYSQL *conn = MySQL::GetConn(link_identifier);
  1043. MYSQL_RES *mysql_result;
  1044. mysql_result = mysql_store_result(conn);
  1045. if (!mysql_result) {
  1046. if (mysql_field_count(conn) > 0) {
  1047. raise_warning("Unable to save result set");
  1048. return false;
  1049. }
  1050. return true;
  1051. }
  1052. return Resource(NEWOBJ(MySQLResult)(mysql_result));
  1053. }
  1054. Variant f_mysql_unbuffered_query(const String& query,
  1055. CVarRef link_identifier /* = null */) {
  1056. return php_mysql_do_query_general(query, link_identifier, false, false);
  1057. }
  1058. Variant f_mysql_db_query(const String& database, const String& query,
  1059. CVarRef link_identifier /* = uninit_null() */) {
  1060. throw NotSupportedException
  1061. (__func__, "Deprecated. Use mysql_query() instead.");
  1062. }
  1063. Variant f_mysql_list_dbs(CVarRef link_identifier /* = null */) {
  1064. MYSQL *conn = MySQL::GetConn(link_identifier);
  1065. if (!conn) return false;
  1066. MYSQL_RES *res = mysql_list_dbs(conn, NULL);
  1067. if (!res) {
  1068. raise_warning("Unable to save MySQL query result");
  1069. return false;
  1070. }
  1071. return Resource(NEWOBJ(MySQLResult)(res));
  1072. }
  1073. Variant f_mysql_list_tables(const String& database,
  1074. CVarRef link_identifier /* = null */) {
  1075. MYSQL *conn = MySQL::GetConn(link_identifier);
  1076. if (!conn) return false;
  1077. if (mysql_select_db(conn, database.data())) {
  1078. return false;
  1079. }
  1080. MYSQL_RES *res = mysql_list_tables(conn, NULL);
  1081. if (!res) {
  1082. raise_warning("Unable to save MySQL query result");
  1083. return false;
  1084. }
  1085. return Resource(NEWOBJ(MySQLResult)(res));
  1086. }
  1087. Variant f_mysql_list_fields(const String& database_name, const String& table_name,
  1088. CVarRef link_identifier /* = uninit_null() */) {
  1089. throw NotSupportedException
  1090. (__func__, "Deprecated. Use mysql_query(SHOW COLUMNS FROM table "
  1091. "[LIKE 'name']) instead.");
  1092. }
  1093. Variant f_mysql_list_processes(CVarRef link_identifier /* = null */) {
  1094. MYSQL *conn = MySQL::GetConn(link_identifier);
  1095. if (!conn) return false;
  1096. MYSQL_RES *res = mysql_list_processes(conn);
  1097. if (!res) {
  1098. raise_warning("Unable to save MySQL query result");
  1099. return false;
  1100. }
  1101. return Resource(NEWOBJ(MySQLResult)(res));
  1102. }
  1103. ///////////////////////////////////////////////////////////////////////////////
  1104. // row operations
  1105. bool f_mysql_data_seek(CVarRef result, int row) {
  1106. MySQLResult *res = get_result(result);
  1107. if (res == NULL) return false;
  1108. return res->seekRow(row);
  1109. }
  1110. #define MYSQL_ASSOC 1 << 0
  1111. #define MYSQL_NUM 1 << 1
  1112. #define MYSQL_BOTH (MYSQL_ASSOC|MYSQL_NUM)
  1113. static Variant php_mysql_fetch_hash(CVarRef result, int result_type) {
  1114. if ((result_type & MYSQL_BOTH) == 0) {
  1115. throw_invalid_argument("result_type: %d", result_type);
  1116. return false;
  1117. }
  1118. MySQLResult *res = get_result(result);
  1119. if (res == NULL) return false;
  1120. Array ret;
  1121. if (res->isLocalized()) {
  1122. if (!res->fetchRow()) return false;
  1123. for (int i = 0; i < res->getFieldCount(); i++) {
  1124. if (result_type & MYSQL_NUM) {
  1125. ret.set(i, res->getField(i));
  1126. }
  1127. if (result_type & MYSQL_ASSOC) {
  1128. MySQLFieldInfo *info = res->getFieldInfo(i);
  1129. ret.set(info->name, res->getField(i));
  1130. }
  1131. }
  1132. return ret;
  1133. }
  1134. MYSQL_RES *mysql_result = res->get();
  1135. MYSQL_ROW mysql_row = mysql_fetch_row(mysql_result);
  1136. if (!mysql_row) {
  1137. return false;
  1138. }
  1139. unsigned long *mysql_row_lengths = mysql_fetch_lengths(mysql_result);
  1140. if (!mysql_row_lengths) {
  1141. return false;
  1142. }
  1143. mysql_field_seek(mysql_result, 0);
  1144. MYSQL_FIELD *mysql_field;
  1145. int i;
  1146. for (mysql_field = mysql_fetch_field(mysql_result), i = 0; mysql_field;
  1147. mysql_field = mysql_fetch_field(mysql_result), i++) {
  1148. Variant data;
  1149. if (mysql_row[i]) {
  1150. data = mysql_makevalue(String(mysql_row[i], mysql_row_lengths[i],
  1151. CopyString), mysql_field);
  1152. }
  1153. if (result_type & MYSQL_NUM) {
  1154. ret.set(i, data);
  1155. }
  1156. if (result_type & MYSQL_ASSOC) {
  1157. ret.set(String(mysql_field->name, CopyString), data);
  1158. }
  1159. }
  1160. return ret;
  1161. }
  1162. /* The mysql_*_nonblocking calls are Facebook extensions to
  1163. libmysqlclient; for now, protect with an ifdef. Once open sourced,
  1164. the client will be detectable via its own ifdef. */
  1165. #ifdef FACEBOOK
  1166. const int64_t k_ASYNC_OP_INVALID = 0;
  1167. const int64_t k_ASYNC_OP_UNSET = ASYNC_OP_UNSET;
  1168. const int64_t k_ASYNC_OP_CONNECT = ASYNC_OP_CONNECT;
  1169. const int64_t k_ASYNC_OP_QUERY = ASYNC_OP_QUERY;
  1170. const int64_t k_ASYNC_OP_FETCH_ROW = ASYNC_OP_FETCH_ROW;
  1171. bool MySQL::async_connect(const String& host, int port, const String& socket,
  1172. const String& username, const String& password,
  1173. const String& database) {
  1174. if (m_conn == NULL) {
  1175. m_conn = create_new_conn();
  1176. }
  1177. if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLStats) {
  1178. ServerStats::Log("sql.conn", 1);
  1179. }
  1180. IOStatusHelper io("mysql::async_connect", host.data(), port);
  1181. m_xaction_count = 0;
  1182. if (!mysql_real_connect_nonblocking_init(
  1183. m_conn, host.data(), username.data(), password.data(),
  1184. (database.empty() ? NULL : database.data()), port,
  1185. socket.empty() ? NULL : socket.data(), CLIENT_INTERACTIVE)) {
  1186. return false;
  1187. }
  1188. return true;
  1189. }
  1190. Variant f_mysql_async_connect_start(const String& server /* = null_string */,
  1191. const String& username /* = null_string */,
  1192. const String& password /* = null_string */,
  1193. const String& database /* = null_string */) {
  1194. return php_mysql_do_connect(server, username, password, database,
  1195. 0, false, true, 0, 0);
  1196. }
  1197. bool f_mysql_async_connect_completed(CVarRef link_identifier) {
  1198. MySQL* mySQL = MySQL::Get(link_identifier);
  1199. if (!mySQL) {
  1200. raise_warning("supplied argument is not a valid MySQL-Link resource");
  1201. return true;
  1202. }
  1203. MYSQL* conn = mySQL->get();
  1204. if (conn->async_op_status != ASYNC_OP_CONNECT) {
  1205. // Don't warn if we're in UNSET state (ie between queries, etc)
  1206. if (conn->async_op_status != ASYNC_OP_UNSET) {
  1207. raise_warning("runtime/ext_mysql: no pending async connect in progress");
  1208. }
  1209. return true;
  1210. }
  1211. int error = 0;
  1212. int status = mysql_real_connect_nonblocking_run(conn, &error);
  1213. if (error) {
  1214. return true;
  1215. }
  1216. return status == ASYNC_CLIENT_COMPLETE;
  1217. }
  1218. bool f_mysql_async_query_start(const String& query, CVarRef link_identifier) {
  1219. MYSQL* conn = MySQL::GetConn(link_identifier);
  1220. if (!conn) {
  1221. return false;
  1222. }
  1223. if (conn->async_op_status != ASYNC_OP_UNSET) {
  1224. raise_warning("runtime/ext_mysql: attempt to run async query while async "
  1225. "operation already pending");
  1226. return false;
  1227. }
  1228. Variant ret = php_mysql_do_query_general(query, link_identifier, true, true);
  1229. if (ret.getRawType() != KindOfInt64) {
  1230. raise_warning("runtime/ext_mysql: unexpected return from "
  1231. "php_mysql_do_query_general");
  1232. return false;
  1233. }
  1234. return true;
  1235. }
  1236. Variant f_mysql_async_query_result(CVarRef link_identifier) {
  1237. MySQL* mySQL = MySQL::Get(link_identifier);
  1238. if (!mySQL) {
  1239. raise_warning("supplied argument is not a valid MySQL-Link resource");
  1240. return false;
  1241. }
  1242. MYSQL* conn = mySQL->get();
  1243. if (!conn || conn->async_op_status != ASYNC_OP_QUERY) {
  1244. raise_warning("runtime/ext_mysql: attempt to check query result when query "
  1245. "not executing");
  1246. return false;
  1247. }
  1248. int error = 0;
  1249. int status = mysql_real_query_nonblocking_run(conn, &error);
  1250. if (error) {
  1251. return false;
  1252. }
  1253. if (status != ASYNC_CLIENT_COMPLETE) {
  1254. return false;
  1255. }
  1256. MYSQL_RES* mysql_result = mysql_use_result(conn);
  1257. MySQLResult *r = NEWOBJ(MySQLResult)(mysql_result);
  1258. r->setAsyncConnection(mySQL);
  1259. Resource ret(r);
  1260. return ret;
  1261. }
  1262. bool f_mysql_async_query_completed(CVarRef result) {
  1263. MySQLResult *res = result.toResource().getTyped<MySQLResult>
  1264. (!RuntimeOption::ThrowBadTypeExceptions,
  1265. !RuntimeOption::ThrowBadTypeExceptions);
  1266. return !res || res->get() == NULL;
  1267. }
  1268. Variant f_mysql_async_fetch_array(CVarRef result, int result_type /* = 1 */) {
  1269. if ((result_type & MYSQL_BOTH) == 0) {
  1270. throw_invalid_argument("result_type: %d", result_type);
  1271. return false;
  1272. }
  1273. MySQLResult* res = get_result(result);
  1274. if (!res) {
  1275. return false;
  1276. }
  1277. MYSQL_RES* mysql_result = res->get();
  1278. if (!mysql_result) {
  1279. raise_warning("invalid parameter to mysql_async_fetch_array");
  1280. return false;
  1281. }
  1282. MYSQL_ROW mysql_row = NULL;
  1283. int status = mysql_fetch_row_nonblocking(&mysql_row, mysql_result);
  1284. // Last row, or no row yet available.
  1285. if (status == ASYNC_CLIENT_NOT_READY) {
  1286. return false;
  1287. }
  1288. if (mysql_row == NULL) {
  1289. res->close();
  1290. return false;
  1291. }
  1292. unsigned long *mysql_row_lengths = mysql_fetch_lengths(mysql_result);
  1293. if (!mysql_row_lengths) {
  1294. return false;
  1295. }
  1296. mysql_field_seek(mysql_result, 0);
  1297. Array ret;
  1298. MYSQL_FIELD *mysql_field;
  1299. int i;
  1300. for (mysql_field = mysql_fetch_field(mysql_result), i = 0; mysql_field;
  1301. mysql_field = mysql_fetch_field(mysql_result), i++) {
  1302. Variant data;
  1303. if (mysql_row[i]) {
  1304. data = mysql_makevalue(String(mysql_row[i], mysql_row_lengths[i],
  1305. CopyString), mysql_field);
  1306. }
  1307. if (result_type & MYSQL_NUM) {
  1308. ret.set(i, data);
  1309. }
  1310. if (result_type & MYSQL_ASSOC) {
  1311. ret.set(String(mysql_field->name, CopyString), data);
  1312. }
  1313. }
  1314. return ret;
  1315. }
  1316. // This function takes an array of arrays, each of which is of the
  1317. // form array($dbh, ...). The only thing that matters in the inner
  1318. // arrays is the first element being a MySQL instance. It then
  1319. // procedes to block for up to 'timeout' seconds, waiting for the
  1320. // first actionable descriptor(s), which it then returns in the form
  1321. // of the original arrays passed in. The intention is the caller
  1322. // would include other information they care about in the tail of the
  1323. // array so they can decide how to act on the
  1324. // potentially-now-queryable descriptors.
  1325. //
  1326. // This function is a poor shadow of how the async library can be
  1327. // used; for more complex cases, we'd use libevent and share our event
  1328. // loop with other IO operations such as memcache ops, thrift calls,
  1329. // etc. That said, this function is reasonably efficient for most use
  1330. // cases.
  1331. Variant f_mysql_async_wait_actionable(CVarRef items, double timeout) {
  1332. size_t count = items.toArray().size();
  1333. if (count == 0 || timeout < 0) {
  1334. return Array::Create();
  1335. }
  1336. struct pollfd* fds = (struct pollfd*)calloc(count, sizeof(struct pollfd));
  1337. SCOPE_EXIT { free(fds); };
  1338. // Walk our input, determine what kind of poll() operation is
  1339. // necessary for the descriptor in question, and put an entry into
  1340. // fds.
  1341. int nfds = 0;
  1342. for (ArrayIter iter(items.toArray()); iter; ++iter) {
  1343. Array entry = iter.second().toArray();
  1344. if (entry.size() < 1) {
  1345. raise_warning("element %d did not have at least one entry",
  1346. nfds);
  1347. return Array::Create();
  1348. }
  1349. MySQL* mySQL = entry.rvalAt(0).toResource().getTyped<MySQL>();
  1350. MYSQL* conn = mySQL->get();
  1351. if (conn->async_op_status == ASYNC_OP_UNSET) {
  1352. raise_warning("runtime/ext_mysql: no pending async operation in "
  1353. "progress");
  1354. return Array::Create();
  1355. }
  1356. pollfd* fd = &fds[nfds++];
  1357. fd->fd = conn->net.fd;
  1358. if (conn->net.nonblocking_status == NET_NONBLOCKING_READ) {
  1359. fd->events = POLLIN;
  1360. } else {
  1361. fd->events = POLLOUT;
  1362. }
  1363. fd->revents = 0;
  1364. }
  1365. // The poll itself; either the timeout is hit or one or more of the
  1366. // input fd's is ready.
  1367. int timeout_millis = static_cast<long>(timeout * 1000);
  1368. int res = poll(fds, nfds, timeout_millis);
  1369. if (res == -1) {
  1370. raise_warning("unable to poll [%d]: %s", errno,
  1371. folly::errnoStr(errno).c_str());
  1372. return Array::Create();
  1373. }
  1374. // Now just find the ones that are ready, and copy the corresponding
  1375. // arrays from our input array into our return value.
  1376. Array ret = Array::Create();
  1377. nfds = 0;
  1378. for (ArrayIter iter(items.toArray()); iter; ++iter) {
  1379. Array entry = iter.second().toArray();
  1380. if (entry.size() < 1) {
  1381. raise_warning("element %d did not have at least one entry",
  1382. nfds);
  1383. return Array::Create();
  1384. }
  1385. MySQL* mySQL = entry.rvalAt(0).toResource().getTyped<MySQL>();
  1386. MYSQL* conn = mySQL->get();
  1387. pollfd* fd = &fds[nfds++];
  1388. if (fd->fd != conn->net.fd) {
  1389. raise_warning("poll returned events out of order wtf");
  1390. continue;
  1391. }
  1392. if (fd->revents != 0) {
  1393. ret.append(iter.second());
  1394. }
  1395. }
  1396. return ret;
  1397. }
  1398. int64_t f_mysql_async_status(CVarRef link_identifier) {
  1399. MySQL *mySQL = MySQL::Get(link_identifier);
  1400. if (!mySQL || !mySQL->get()) {
  1401. raise_warning("supplied argument is not a valid MySQL-Link resource");
  1402. return -1;
  1403. }
  1404. return mySQL->get()->async_op_status;
  1405. }
  1406. #else // FACEBOOK
  1407. // Bogus values for non-facebook libmysqlclients.
  1408. const int64_t k_ASYNC_OP_INVALID = 0;
  1409. const int64_t k_ASYNC_OP_UNSET = -1;
  1410. const int64_t k_ASYNC_OP_CONNECT = -2;
  1411. const int64_t k_ASYNC_OP_QUERY = -3;
  1412. const int64_t k_ASYNC_OP_FETCH_ROW = -4;
  1413. Variant f_mysql_async_connect_start(const String& server,
  1414. const String& username,
  1415. const String& password,
  1416. const String& database) {
  1417. throw NotImplementedException(__func__);
  1418. }
  1419. bool f_mysql_async_connect_completed(CVarRef link_identifier) {
  1420. throw NotImplementedException(__func__);
  1421. }
  1422. bool f_mysql_async_query_start(const String& query, CVarRef link_identifier) {
  1423. throw NotImplementedException(__func__);
  1424. }
  1425. Variant f_mysql_async_query_result(CVarRef link_identifier) {
  1426. throw NotImplementedException(__func__);
  1427. }
  1428. bool f_mysql_async_query_completed(CVarRef result) {
  1429. throw NotImplementedException(__func__);
  1430. }
  1431. Variant f_mysql_async_fetch_array(CVarRef result, int result_type /* = 1 */) {
  1432. throw NotImplementedException(__func__);
  1433. }
  1434. Variant f_mysql_async_wait_actionable(CVarRef items, double timeout) {
  1435. throw NotImplementedException(__func__);
  1436. }
  1437. int64_t f_mysql_async_status(CVarRef link_identifier) {
  1438. throw NotImplementedException(__func__);
  1439. }
  1440. #endif
  1441. Variant f_mysql_fetch_row(CVarRef result) {
  1442. return php_mysql_fetch_hash(result, MYSQL_NUM);
  1443. }
  1444. Variant f_mysql_fetch_assoc(CVarRef result) {
  1445. return php_mysql_fetch_hash(result, MYSQL_ASSOC);
  1446. }
  1447. Variant f_mysql_fetch_array(CVarRef result, int result_type /* = 3 */) {
  1448. return php_mysql_fetch_hash(result, result_type);
  1449. }
  1450. Variant f_mysql_fetch_object(CVarRef result,
  1451. const String& class_name /* = "stdClass" */,
  1452. CArrRef params /* = null */) {
  1453. Variant properties = php_mysql_fetch_hash(result, MYSQL_ASSOC);
  1454. if (!same(properties, false)) {
  1455. Object obj = create_object(class_name, params);
  1456. obj->o_setArray(properties.toArray());
  1457. return obj;
  1458. }
  1459. return false;
  1460. }
  1461. Variant f_mysql_fetch_lengths(CVarRef result) {
  1462. MySQLResult *res = get_result(result);
  1463. if (res == NULL) return false;
  1464. if (res->isLocalized()) {
  1465. if (!res->isRowReady()) return false;
  1466. Array ret;
  1467. for (int i = 0; i < res->getFieldCount(); i++) {
  1468. MySQLFieldInfo *info = res->getFieldInfo(i);
  1469. if (info->type == MYSQL_TYPE_YEAR) {
  1470. // special case for years, because of leading zeros
  1471. ret.set(i, info->length);
  1472. } else {
  1473. // convert fields back to Strings to get lengths
  1474. ret.set(i, res->getField(i).toString().length());
  1475. }
  1476. }
  1477. return ret;
  1478. }
  1479. MYSQL_RES *mysql_result = res->get();
  1480. unsigned long *lengths = mysql_fetch_lengths(mysql_result);
  1481. if (!lengths) {
  1482. return false;
  1483. }
  1484. Array ret;
  1485. int num_fields = mysql_num_fields(mysql_result);
  1486. for (int i = 0; i < num_fields; i++) {
  1487. ret.set(i, (int)lengths[i]);
  1488. }
  1489. return ret;
  1490. }
  1491. Variant f_mysql_result(CVarRef result, int row,
  1492. CVarRef field /* = null_variant */) {
  1493. MySQLResult *res = get_result(result);
  1494. if (res == NULL) return false;
  1495. MYSQL_RES *mysql_result = NULL;
  1496. MYSQL_ROW sql_row = NULL;
  1497. unsigned long *sql_row_lengths = NULL;
  1498. if (res->isLocalized()) {
  1499. if (!res->seekRow(row)) return false;
  1500. if (!res->fetchRow()) return false;
  1501. } else {
  1502. mysql_result = res->get();
  1503. if (row < 0 || row >= (int)mysql_num_rows(mysql_result)) {
  1504. raise_warning("Unable to jump to row %d on MySQL result index %d",
  1505. row, result.toResource()->o_getId());
  1506. return false;
  1507. }
  1508. mysql_data_seek(mysql_result, row);
  1509. sql_row = mysql_fetch_row(mysql_result);
  1510. if (!sql_row) {
  1511. return false;
  1512. }
  1513. sql_row_lengths = mysql_fetch_lengths(mysql_result);
  1514. if (!sql_row_lengths) {
  1515. return false;
  1516. }
  1517. }
  1518. int field_offset = 0;
  1519. if (!field.isNull()) {
  1520. if (field.isString()) {
  1521. String sfield = field.toString();
  1522. const char *tmp = strchr(sfield.data(), '.');
  1523. String table_name, field_name;
  1524. if (tmp) {
  1525. int pos = tmp - sfield.data();
  1526. table_name = sfield.substr(0, pos);
  1527. field_name = sfield.substr(pos + 1);
  1528. } else {
  1529. field_name = sfield;
  1530. }
  1531. int i = 0;
  1532. bool found = false;
  1533. res->seekField(0);
  1534. while (i < res->getFieldCount()) {
  1535. MySQLFieldInfo *info = res->getFieldInfo(i);
  1536. if ((table_name.empty() || table_name.same(info->table)) &&
  1537. field_name.same(info->name)) {
  1538. field_offset = i;
  1539. found = true;
  1540. break;
  1541. }
  1542. i++;
  1543. }
  1544. if (!found) { /* no match found */
  1545. raise_warning("%s%s%s not found in MySQL result index %d",
  1546. table_name.data(), (table_name.empty() ? "" : "."),
  1547. field_name.data(), result.toResource()->o_getId());
  1548. return false;
  1549. }
  1550. } else {
  1551. field_offset = field.toInt32();
  1552. if (field_offset < 0 ||
  1553. field_offset >= (int)res->getFieldCount()) {
  1554. raise_warning("Bad column offset specified");
  1555. return false;
  1556. }
  1557. }
  1558. }
  1559. if (res->isLocalized()) {
  1560. Variant f = res->getField(field_offset);
  1561. if (!f.isNull()) {
  1562. return f.toString();
  1563. }
  1564. } else {
  1565. if (sql_row[field_offset]) {
  1566. return String(sql_row[field_offset], sql_row_lengths[field_offset],
  1567. CopyString);
  1568. }
  1569. }
  1570. return uninit_null();
  1571. }
  1572. ///////////////////////////////////////////////////////////////////////////////
  1573. // result functions
  1574. Variant f_mysql_db_name(CVarRef result, int row,
  1575. CVarRef field /* = null_variant */) {
  1576. return f_mysql_result(result, row, field);
  1577. }
  1578. Variant f_mysql_tablename(CVarRef result, int i) {
  1579. return f_mysql_result(result, i);
  1580. }
  1581. Variant f_mysql_num_fields(CVarRef result) {
  1582. MySQLResult *res = get_result(result);
  1583. if (res) {
  1584. return res->getFieldCount();
  1585. }
  1586. return false;
  1587. }
  1588. Variant f_mysql_num_rows(CVarRef result) {
  1589. MySQLResult *res = get_result(result);
  1590. if (res) {
  1591. return res->getRowCount();
  1592. }
  1593. return false;
  1594. }
  1595. Variant f_mysql_free_result(CVarRef result) {
  1596. MySQLResult *res = get_result(result);
  1597. if (res) {
  1598. res->close();
  1599. return true;
  1600. }
  1601. return false;
  1602. }
  1603. ///////////////////////////////////////////////////////////////////////////////
  1604. // field info
  1605. Variant f_mysql_fetch_field(CVarRef result, int field /* = -1 */) {
  1606. MySQLResult *res = get_result(result);
  1607. if (res == NULL) return false;
  1608. if (field != -1) {
  1609. if (!res->seekField(field)) return false;
  1610. }
  1611. MySQLFieldInfo *info;
  1612. if (!(info = res->fetchFieldInfo())) return false;
  1613. Object obj(SystemLib::AllocStdClassObject());
  1614. obj->o_set("name", info->name);
  1615. obj->o_set("table", info->table);
  1616. obj->o_set("def", info->def);
  1617. obj->o_set("max_length", (int)info->max_length);
  1618. obj->o_set("not_null", IS_NOT_NULL(info->flags)? 1 : 0);
  1619. obj->o_set("primary_key", IS_PRI_KEY(info->flags)? 1 : 0);
  1620. obj->o_set("multiple_key", info->flags & MULTIPLE_KEY_FLAG? 1 : 0);
  1621. obj->o_set("unique_key", info->flags & UNIQUE_KEY_FLAG? 1 : 0);
  1622. obj->o_set("numeric", IS_NUM(info->type)? 1 : 0);
  1623. obj->o_set("blob", IS_BLOB(info->flags)? 1 : 0);
  1624. obj->o_set("type", php_mysql_get_field_name(info->type));
  1625. obj->o_set("unsigned", info->flags & UNSIGNED_FLAG? 1 : 0);
  1626. obj->o_set("zerofill", info->flags & ZEROFILL_FLAG? 1 : 0);
  1627. return obj;
  1628. }
  1629. bool f_mysql_field_seek(CVarRef result, int field /* = 0 */) {
  1630. MySQLResult *res = get_result(result);
  1631. if (res == NULL) return false;
  1632. res->seekField(field);
  1633. return true;
  1634. }
  1635. Variant f_mysql_field_name(CVarRef result, int field /* = 0 */) {
  1636. return php_mysql_field_info(result, field, PHP_MYSQL_FIELD_NAME);
  1637. }
  1638. Variant f_mysql_field_table(CVarRef result, int field /* = 0 */) {
  1639. return php_mysql_field_info(result, field, PHP_MYSQL_FIELD_TABLE);
  1640. }
  1641. Variant f_mysql_field_len(CVarRef result, int field /* = 0 */) {
  1642. return php_mysql_field_info(result, field, PHP_MYSQL_FIELD_LEN);
  1643. }
  1644. Variant f_mysql_field_type(CVarRef result, int field /* = 0 */) {
  1645. return php_mysql_field_info(result, field, PHP_MYSQL_FIELD_TYPE);
  1646. }
  1647. Variant f_mysql_field_flags(CVarRef result, int field /* = 0 */) {
  1648. return php_mysql_field_info(result, field, PHP_MYSQL_FIELD_FLAGS);
  1649. }
  1650. ///////////////////////////////////////////////////////////////////////////////
  1651. // MySQLResult
  1652. void MySQLResult::addRow() {
  1653. m_row_count++;
  1654. m_rows->push_back(smart::vector<Variant>());
  1655. m_rows->back().reserve(getFieldCount());
  1656. }
  1657. void MySQLResult::addField(Variant&& value) {
  1658. m_rows->back().push_back(std::move(value));
  1659. }
  1660. void MySQLResult::setFieldCount(int64_t fields) {
  1661. m_field_count = fields;
  1662. assert(!m_fields);
  1663. m_fields = smart_new_array<MySQLFieldInfo>(fields);
  1664. }
  1665. void MySQLResult::setFieldInfo(int64_t f, MYSQL_FIELD *field) {
  1666. MySQLFieldInfo &info = m_fields[f];
  1667. info.name = String(field->name, CopyString);
  1668. info.table = String(field->table, CopyString);
  1669. info.def = String(field->def, CopyString);
  1670. info.max_length = (int64_t)field->max_length;
  1671. info.length = (int64_t)field->length;
  1672. info.type = (int)field->type;
  1673. info.flags = field->flags;
  1674. }
  1675. MySQLFieldInfo *MySQLResult::getFieldInfo(int64_t field) {
  1676. if (field < 0 || field >= getFieldCount()) {
  1677. return NULL;
  1678. }
  1679. if (!m_localized && !m_fields) {
  1680. if (m_res->fields == NULL) return NULL;
  1681. // cache field info
  1682. setFieldCount(getFieldCount());
  1683. for (int i = 0; i < getFieldCount(); i++) {
  1684. setFieldInfo(i, m_res->fields + i);
  1685. }
  1686. }
  1687. return m_fields + field;
  1688. }
  1689. Variant MySQLResult::getField(int64_t field) const {
  1690. if (!m_localized || field < 0 || field >= (int64_t)m_current_row->size()) {
  1691. return uninit_null();
  1692. }
  1693. return (*m_current_row)[field];
  1694. }
  1695. int64_t MySQLResult::getFieldCount() const {
  1696. if (!m_localized) {
  1697. return (int64_t)mysql_num_fields(m_res);
  1698. }
  1699. return m_field_count;
  1700. }
  1701. int64_t MySQLResult::getRowCount() const {
  1702. if (!m_localized) {
  1703. return (int64_t)mysql_num_rows(m_res);
  1704. }
  1705. return m_row_count;
  1706. }
  1707. bool MySQLResult::seekRow(int64_t row) {
  1708. if (row < 0 || row >= getRowCount()) {
  1709. raise_warning("Unable to jump to row %" PRId64 " on MySQL result index %d",
  1710. row, o_getId());
  1711. return false;
  1712. }
  1713. if (!m_localized) {
  1714. mysql_data_seek(m_res, (my_ulonglong)row);
  1715. } else {
  1716. m_current_row = m_rows->begin();
  1717. for (int i = 0; i < row; i++) m_current_row++;
  1718. m_row_ready = false;
  1719. }
  1720. return true;
  1721. }
  1722. bool MySQLResult::fetchRow() {
  1723. if (m_current_row != m_rows->end()) m_current_row++;
  1724. if (m_current_row != m_rows->end()) {
  1725. m_row_ready = true;
  1726. return true;
  1727. }
  1728. return false;
  1729. }
  1730. bool MySQLResult::seekField(int64_t field) {
  1731. if (field < 0 || field >= getFieldCount()) {
  1732. raise_warning("Field %" PRId64 " is invalid for MySQL result index %d",
  1733. field, o_getId());
  1734. return false;
  1735. }
  1736. if (!m_localized) {
  1737. mysql_field_seek(m_res, (MYSQL_FIELD_OFFSET)field);
  1738. }
  1739. m_current_field = field - 1;
  1740. return true;
  1741. }
  1742. MySQLFieldInfo *MySQLResult::fetchFieldInfo() {
  1743. if (!m_localized) {
  1744. mysql_fetch_field(m_res);
  1745. }
  1746. if (m_current_field < getFieldCount()) m_current_field++;
  1747. return getFieldInfo(m_current_field);
  1748. }
  1749. ///////////////////////////////////////////////////////////////////////////////
  1750. }