PageRenderTime 28ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/runtime/ext/pdo_mysql.cpp

https://gitlab.com/Blueprint-Marketing/hhvm
C++ | 1372 lines | 1127 code | 173 blank | 72 comment | 226 complexity | d533f34b750b514594a1382e0c31b97c MD5 | raw file
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010-2014 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/pdo_mysql.h"
  18. #include "hphp/runtime/ext/stream/ext_stream.h"
  19. #include "hphp/runtime/base/comparisons.h"
  20. #include "hphp/runtime/base/ini-setting.h"
  21. #include "hphp/runtime/vm/jit/translator-inline.h"
  22. #include "hphp/util/network.h"
  23. #include "mysql.h"
  24. #include <memory>
  25. #ifdef PHP_MYSQL_UNIX_SOCK_ADDR
  26. #ifdef MYSQL_UNIX_ADDR
  27. #undef MYSQL_UNIX_ADDR
  28. #endif
  29. #define MYSQL_UNIX_ADDR PHP_MYSQL_UNIX_SOCK_ADDR
  30. #endif
  31. namespace HPHP {
  32. class PDOMySqlStatement;
  33. IMPLEMENT_DEFAULT_EXTENSION_VERSION(pdo_mysql, 1.0.2);
  34. ///////////////////////////////////////////////////////////////////////////////
  35. struct PDOMySqlError {
  36. PDOMySqlError() : file(NULL), line(0), errcode(0), errmsg(NULL) {
  37. }
  38. const char *file;
  39. int line;
  40. unsigned int errcode;
  41. char *errmsg;
  42. };
  43. struct PDOMySqlConnection : PDOConnection {
  44. PDOMySqlConnection();
  45. virtual ~PDOMySqlConnection();
  46. bool create(const Array& options) override;
  47. bool support(SupportedMethod method) override;
  48. bool closer() override;
  49. bool preparer(const String& sql, sp_PDOStatement *stmt,
  50. const Variant& options) override;
  51. int64_t doer(const String& sql) override;
  52. bool quoter(const String& input, String &quoted,
  53. PDOParamType paramtype) override;
  54. bool begin() override;
  55. bool commit() override;
  56. bool rollback() override;
  57. bool setAttribute(int64_t attr, const Variant& value) override;
  58. String lastId(const char *name) override;
  59. bool fetchErr(PDOStatement* stmt, Array &info) override;
  60. int getAttribute(int64_t attr, Variant &value) override;
  61. bool checkLiveness() override;
  62. bool buffered() const { return m_buffered; }
  63. unsigned long max_buffer_size() const { return m_max_buffer_size; }
  64. bool fetch_table_names() const { return m_fetch_table_names; }
  65. int handleError(const char *file, int line,
  66. PDOMySqlStatement *stmt = nullptr);
  67. private:
  68. MYSQL* m_server;
  69. unsigned m_attached : 1;
  70. unsigned m_buffered : 1;
  71. unsigned m_emulate_prepare : 1;
  72. unsigned m_fetch_table_names : 1;
  73. unsigned long m_max_buffer_size;
  74. PDOMySqlError m_einfo;
  75. };
  76. struct PDOMySqlResource : PDOResource {
  77. explicit PDOMySqlResource(std::shared_ptr<PDOMySqlConnection> conn)
  78. : PDOResource(std::dynamic_pointer_cast<PDOConnection>(conn))
  79. {}
  80. std::shared_ptr<PDOMySqlConnection> conn() const {
  81. return std::dynamic_pointer_cast<PDOMySqlConnection>(m_conn);
  82. }
  83. };
  84. struct PDOMySqlStatement : PDOStatement {
  85. DECLARE_RESOURCE_ALLOCATION(PDOMySqlStatement);
  86. PDOMySqlStatement(SmartPtr<PDOMySqlResource>&& conn, MYSQL* server);
  87. virtual ~PDOMySqlStatement();
  88. bool create(const String& sql, const Array& options);
  89. bool support(SupportedMethod method) override;
  90. bool executer() override;
  91. bool fetcher(PDOFetchOrientation ori, long offset) override;
  92. bool describer(int colno) override;
  93. bool getColumn(int colno, Variant &value) override;
  94. bool paramHook(PDOBoundParam* param, PDOParamEvent event_type) override;
  95. bool getColumnMeta(int64_t colno, Array &return_value) override;
  96. bool nextRowset() override;
  97. bool cursorCloser() override;
  98. MYSQL_STMT *stmt() { return m_stmt;}
  99. private:
  100. std::shared_ptr<PDOMySqlConnection> m_conn;
  101. MYSQL* m_server;
  102. MYSQL_RES* m_result;
  103. const MYSQL_FIELD* m_fields;
  104. MYSQL_ROW m_current_data;
  105. long* m_current_lengths;
  106. PDOMySqlError m_einfo;
  107. MYSQL_STMT* m_stmt;
  108. int m_num_params;
  109. MYSQL_BIND* m_params;
  110. my_bool* m_in_null;
  111. unsigned long* m_in_length;
  112. MYSQL_BIND* m_bound_result;
  113. my_bool* m_out_null;
  114. unsigned long* m_out_length;
  115. unsigned int m_params_given;
  116. unsigned m_max_length:1;
  117. void setRowCount();
  118. bool executePrepared();
  119. int handleError(const char* file, int line);
  120. };
  121. ///////////////////////////////////////////////////////////////////////////////
  122. /* For the convenience of drivers, this function will parse a data source
  123. * string, of the form "name=value; name2=value2" and populate variables
  124. * according to the data you pass in and array of pdo_data_src_parser structures */
  125. struct pdo_data_src_parser {
  126. const char *optname;
  127. char *optval;
  128. int freeme;
  129. };
  130. static int php_pdo_parse_data_source(const char *data_source,
  131. int data_source_len,
  132. struct pdo_data_src_parser *parsed,
  133. int nparams) {
  134. int i, j;
  135. int valstart = -1;
  136. int semi = -1;
  137. int optstart = 0;
  138. int nlen;
  139. int n_matches = 0;
  140. i = 0;
  141. while (i < data_source_len) {
  142. /* looking for NAME= */
  143. if (data_source[i] == '\0') {
  144. break;
  145. }
  146. if (data_source[i] != '=') {
  147. ++i;
  148. continue;
  149. }
  150. valstart = ++i;
  151. /* now we're looking for VALUE; or just VALUE<NUL> */
  152. semi = -1;
  153. while (i < data_source_len) {
  154. if (data_source[i] == '\0') {
  155. semi = i++;
  156. break;
  157. }
  158. if (data_source[i] == ';') {
  159. semi = i++;
  160. break;
  161. }
  162. ++i;
  163. }
  164. if (semi == -1) {
  165. semi = i;
  166. }
  167. /* find the entry in the array */
  168. nlen = valstart - optstart - 1;
  169. for (j = 0; j < nparams; j++) {
  170. if (0 == strncmp(data_source + optstart, parsed[j].optname, nlen) &&
  171. parsed[j].optname[nlen] == '\0') {
  172. /* got a match */
  173. if (parsed[j].freeme) {
  174. free(parsed[j].optval);
  175. }
  176. parsed[j].optval = strndup(data_source + valstart, semi - valstart);
  177. parsed[j].freeme = 1;
  178. ++n_matches;
  179. break;
  180. }
  181. }
  182. while (i < data_source_len && isspace(data_source[i])) {
  183. i++;
  184. }
  185. optstart = i;
  186. }
  187. return n_matches;
  188. }
  189. static long pdo_attr_lval(const Array& options, int opt, long defaultValue) {
  190. if (options.exists(opt)) {
  191. return options[opt].toInt64();
  192. }
  193. return defaultValue;
  194. }
  195. static String pdo_attr_strval(const Array& options, int opt, const char *def) {
  196. if (options.exists(opt)) {
  197. return options[opt].toString();
  198. }
  199. if (def) {
  200. return def;
  201. }
  202. return String();
  203. }
  204. ///////////////////////////////////////////////////////////////////////////////
  205. PDOMySqlConnection::PDOMySqlConnection()
  206. : m_server(NULL), m_attached(0), m_buffered(0), m_emulate_prepare(0),
  207. m_fetch_table_names(0), m_max_buffer_size(0) {
  208. }
  209. PDOMySqlConnection::~PDOMySqlConnection() {
  210. if (m_server) {
  211. mysql_close(m_server);
  212. }
  213. if (m_einfo.errmsg) {
  214. free(m_einfo.errmsg);
  215. }
  216. }
  217. const StaticString s_localhost("localhost");
  218. const std::string s_default_socket_option("pdo_mysql.default_socket");
  219. bool PDOMySqlConnection::create(const Array& options) {
  220. int i, ret = 0;
  221. char *unix_socket = nullptr;
  222. unsigned int port = 3306;
  223. char *dbname;
  224. char *charset = nullptr;
  225. char *default_socket = nullptr;
  226. std::string default_socket_string;
  227. struct pdo_data_src_parser vars[] = {
  228. { "charset", nullptr, 0 },
  229. { "dbname", "", 0 },
  230. { "host", "localhost", 0 },
  231. { "port", "3306", 0 },
  232. { "unix_socket", MYSQL_UNIX_ADDR, 0 },
  233. };
  234. int connect_opts = 0
  235. #ifdef CLIENT_MULTI_RESULTS
  236. |CLIENT_MULTI_RESULTS
  237. #endif
  238. #ifdef CLIENT_MULTI_STATEMENTS
  239. |CLIENT_MULTI_STATEMENTS
  240. #endif
  241. ;
  242. php_pdo_parse_data_source(data_source.data(), data_source.size(), vars, 5);
  243. dbname = vars[1].optval;
  244. // Extract port number from a host in case it's inlined.
  245. String host(vars[2].optval, CopyString);
  246. if (!host.same(s_localhost)) {
  247. HostURL hosturl(host.toCppString(), port);
  248. if (hosturl.isValid()) {
  249. host = String(hosturl.getHost().c_str(), CopyString);
  250. port = hosturl.getPort();
  251. }
  252. }
  253. // Explicit port param overrides the
  254. // implicit one from host.
  255. if (vars[3].optval) {
  256. port = atoi(vars[3].optval);
  257. }
  258. /* handle for the server */
  259. if (!(m_server = mysql_init(NULL))) {
  260. handleError(__FILE__, __LINE__);
  261. goto cleanup;
  262. }
  263. m_max_buffer_size = 1024*1024;
  264. m_buffered = m_emulate_prepare = 1;
  265. charset = vars[0].optval;
  266. /* handle MySQL options */
  267. if (!options.empty()) {
  268. long connect_timeout = pdo_attr_lval(options, PDO_ATTR_TIMEOUT, 30);
  269. long local_infile = pdo_attr_lval(options, PDO_MYSQL_ATTR_LOCAL_INFILE, 0);
  270. String init_cmd, default_file, default_group;
  271. long compress = 0;
  272. m_buffered = pdo_attr_lval(options, PDO_MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
  273. m_emulate_prepare = pdo_attr_lval(options, PDO_MYSQL_ATTR_DIRECT_QUERY,
  274. m_emulate_prepare);
  275. m_emulate_prepare = pdo_attr_lval(options, PDO_ATTR_EMULATE_PREPARES,
  276. m_emulate_prepare);
  277. m_max_buffer_size = pdo_attr_lval(options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE,
  278. m_max_buffer_size);
  279. if (pdo_attr_lval(options, PDO_MYSQL_ATTR_FOUND_ROWS, 0)) {
  280. connect_opts |= CLIENT_FOUND_ROWS;
  281. }
  282. if (pdo_attr_lval(options, PDO_MYSQL_ATTR_IGNORE_SPACE, 0)) {
  283. connect_opts |= CLIENT_IGNORE_SPACE;
  284. }
  285. if (mysql_options(m_server, MYSQL_OPT_CONNECT_TIMEOUT,
  286. (const char *)&connect_timeout)) {
  287. handleError(__FILE__, __LINE__);
  288. goto cleanup;
  289. }
  290. if (mysql_options(m_server, MYSQL_OPT_LOCAL_INFILE,
  291. (const char *)&local_infile)) {
  292. handleError(__FILE__, __LINE__);
  293. goto cleanup;
  294. }
  295. #ifdef MYSQL_OPT_RECONNECT
  296. /* since 5.0.3, the default for this option is 0 if not specified.
  297. * we want the old behaviour */
  298. {
  299. long reconnect = 1;
  300. mysql_options(m_server, MYSQL_OPT_RECONNECT, (const char*)&reconnect);
  301. }
  302. #endif
  303. init_cmd = pdo_attr_strval(options, PDO_MYSQL_ATTR_INIT_COMMAND, NULL);
  304. if (!init_cmd.empty()) {
  305. if (mysql_options(m_server, MYSQL_INIT_COMMAND, init_cmd.data())) {
  306. handleError(__FILE__, __LINE__);
  307. goto cleanup;
  308. }
  309. }
  310. default_file = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_FILE,
  311. NULL);
  312. if (!default_file.empty()) {
  313. if (mysql_options(m_server, MYSQL_READ_DEFAULT_FILE,
  314. default_file.data())) {
  315. handleError(__FILE__, __LINE__);
  316. goto cleanup;
  317. }
  318. }
  319. default_group = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_GROUP,
  320. NULL);
  321. if (!default_group.empty()) {
  322. if (mysql_options(m_server, MYSQL_READ_DEFAULT_GROUP,
  323. default_group.data())) {
  324. handleError(__FILE__, __LINE__);
  325. goto cleanup;
  326. }
  327. }
  328. compress = pdo_attr_lval(options, PDO_MYSQL_ATTR_COMPRESS, 0);
  329. if (compress) {
  330. if (mysql_options(m_server, MYSQL_OPT_COMPRESS, 0)) {
  331. handleError(__FILE__, __LINE__);
  332. goto cleanup;
  333. }
  334. }
  335. }
  336. if (charset) {
  337. if (mysql_options(m_server, MYSQL_SET_CHARSET_NAME, charset)) {
  338. handleError(__FILE__, __LINE__);
  339. goto cleanup;
  340. }
  341. }
  342. if (host.empty() || host.same(s_localhost)) {
  343. if (IniSetting::Get(s_default_socket_option, default_socket_string)) {
  344. default_socket = new char[default_socket_string.size() + 1];
  345. memcpy(default_socket, default_socket_string.c_str(),
  346. default_socket_string.size() + 1);
  347. unix_socket = default_socket;
  348. } else {
  349. unix_socket = vars[4].optval;
  350. }
  351. }
  352. if (mysql_real_connect(m_server, host.c_str(),
  353. username.c_str(), password.c_str(),
  354. dbname, port, unix_socket, connect_opts) == NULL) {
  355. handleError(__FILE__, __LINE__);
  356. goto cleanup;
  357. }
  358. if (!auto_commit) {
  359. mysql_autocommit(m_server, auto_commit);
  360. }
  361. m_attached = 1;
  362. alloc_own_columns = 1;
  363. max_escaped_char_length = 2;
  364. ret = 1;
  365. cleanup:
  366. for (i = 0; i < (int)(sizeof(vars)/sizeof(vars[0])); i++) {
  367. if (vars[i].freeme) {
  368. free(vars[i].optval);
  369. }
  370. }
  371. if (default_socket != nullptr) {
  372. delete[] default_socket;
  373. }
  374. return ret;
  375. }
  376. bool PDOMySqlConnection::support(SupportedMethod method) {
  377. return true;
  378. }
  379. bool PDOMySqlConnection::closer() {
  380. if (m_server) {
  381. mysql_close(m_server);
  382. m_server = NULL;
  383. }
  384. if (m_einfo.errmsg) {
  385. free(m_einfo.errmsg);
  386. m_einfo.errmsg = NULL;
  387. }
  388. return false;
  389. }
  390. int PDOMySqlConnection::handleError(const char *file, int line,
  391. PDOMySqlStatement* stmt) {
  392. PDOErrorType *pdo_err;
  393. PDOMySqlError *einfo = &m_einfo;
  394. if (stmt) {
  395. pdo_err = &stmt->error_code;
  396. } else {
  397. pdo_err = &error_code;
  398. }
  399. if (stmt && stmt->stmt()) {
  400. einfo->errcode = mysql_stmt_errno(stmt->stmt());
  401. } else {
  402. einfo->errcode = mysql_errno(m_server);
  403. }
  404. einfo->file = file;
  405. einfo->line = line;
  406. if (einfo->errmsg) {
  407. free(einfo->errmsg);
  408. einfo->errmsg = NULL;
  409. }
  410. if (einfo->errcode) {
  411. if (einfo->errcode == 2014) {
  412. einfo->errmsg =
  413. strdup("Cannot execute queries while other unbuffered queries are "
  414. "active. Consider using PDOStatement::fetchAll(). "
  415. "Alternatively, if your code is only ever going to run against "
  416. "mysql, you may enable query buffering by setting the "
  417. "PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.");
  418. } else if (einfo->errcode == 2057) {
  419. einfo->errmsg =
  420. strdup("A stored procedure returning result sets of different size "
  421. "was called. This is not supported by libmysql");
  422. } else {
  423. einfo->errmsg = strdup(mysql_error(m_server));
  424. }
  425. } else { /* no error */
  426. strcpy(*pdo_err, PDO_ERR_NONE);
  427. return false;
  428. }
  429. if (stmt && stmt->stmt()) {
  430. strcpy(*pdo_err, mysql_stmt_sqlstate(stmt->stmt()));
  431. } else {
  432. strcpy(*pdo_err, mysql_sqlstate(m_server));
  433. }
  434. if (stmt && stmt->stmt()) {
  435. pdo_raise_impl_error(stmt->dbh, nullptr, pdo_err[0], einfo->errmsg);
  436. } else {
  437. Array info = Array::Create();
  438. info.append(String(*pdo_err, CopyString));
  439. if (stmt) {
  440. stmt->dbh->conn()->fetchErr(stmt, info);
  441. }
  442. throw_pdo_exception(String(*pdo_err, CopyString), info,
  443. "SQLSTATE[%s] [%d] %s",
  444. pdo_err[0], einfo->errcode, einfo->errmsg);
  445. }
  446. return einfo->errcode;
  447. }
  448. bool PDOMySqlConnection::preparer(const String& sql, sp_PDOStatement *stmt,
  449. const Variant& options) {
  450. auto rsrc = makeSmartPtr<PDOMySqlResource>(
  451. std::dynamic_pointer_cast<PDOMySqlConnection>(shared_from_this()));
  452. auto s = makeSmartPtr<PDOMySqlStatement>(std::move(rsrc), m_server);
  453. *stmt = s;
  454. if (m_emulate_prepare) {
  455. return true;
  456. }
  457. int server_version = mysql_get_server_version(m_server);
  458. if (server_version < 40100) {
  459. return true;
  460. }
  461. if (s->create(sql, options.toArray())) {
  462. alloc_own_columns = 1;
  463. return true;
  464. }
  465. stmt->reset();
  466. strcpy(error_code, s->error_code);
  467. return false;
  468. }
  469. int64_t PDOMySqlConnection::doer(const String& sql) {
  470. if (mysql_real_query(m_server, sql.data(), sql.size())) {
  471. handleError(__FILE__, __LINE__);
  472. return -1;
  473. }
  474. my_ulonglong c = mysql_affected_rows(m_server);
  475. if (c == (my_ulonglong) -1) {
  476. handleError(__FILE__, __LINE__);
  477. return m_einfo.errcode ? -1 : 0;
  478. }
  479. /* MULTI_QUERY support - eat up all unfetched result sets */
  480. while (mysql_more_results(m_server)) {
  481. if (mysql_next_result(m_server)) {
  482. return true;
  483. }
  484. MYSQL_RES *result = mysql_store_result(m_server);
  485. if (result) {
  486. mysql_free_result(result);
  487. }
  488. }
  489. return c;
  490. }
  491. bool PDOMySqlConnection::quoter(const String& input, String &quoted,
  492. PDOParamType paramtype) {
  493. String s(2 * input.size() + 3, ReserveString);
  494. char *buf = s.mutableData();
  495. int len = mysql_real_escape_string(m_server, buf + 1,
  496. input.data(), input.size());
  497. len++;
  498. buf[0] = buf[len] = '\'';
  499. len++;
  500. quoted = s.setSize(len);
  501. return true;
  502. }
  503. bool PDOMySqlConnection::begin() {
  504. return doer("START TRANSACTION") >= 0;
  505. }
  506. bool PDOMySqlConnection::commit() {
  507. return mysql_commit(m_server) >= 0;
  508. }
  509. bool PDOMySqlConnection::rollback() {
  510. return mysql_rollback(m_server) >= 0;
  511. }
  512. bool PDOMySqlConnection::setAttribute(int64_t attr, const Variant& value) {
  513. switch (attr) {
  514. case PDO_ATTR_AUTOCOMMIT:
  515. /* ignore if the new value equals the old one */
  516. if (auto_commit ^ value.toBoolean()) {
  517. auto_commit = value.toBoolean();
  518. mysql_autocommit(m_server, auto_commit);
  519. }
  520. return true;
  521. case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
  522. m_buffered = value.toBoolean();
  523. return true;
  524. case PDO_MYSQL_ATTR_DIRECT_QUERY:
  525. case PDO_ATTR_EMULATE_PREPARES:
  526. m_emulate_prepare = value.toBoolean();
  527. return true;
  528. case PDO_ATTR_FETCH_TABLE_NAMES:
  529. m_fetch_table_names = value.toBoolean();
  530. return true;
  531. case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
  532. if (value.toInt64() < 0) {
  533. /* TODO: Johannes, can we throw a warning here? */
  534. m_max_buffer_size = 1024*1024;
  535. } else {
  536. m_max_buffer_size = value.toInt64();
  537. }
  538. return true;
  539. default:
  540. return false;
  541. }
  542. }
  543. String PDOMySqlConnection::lastId(const char *name) {
  544. return (int64_t)mysql_insert_id(m_server);
  545. }
  546. bool PDOMySqlConnection::fetchErr(PDOStatement *stmt, Array &info) {
  547. if (m_einfo.errcode) {
  548. info.append((int64_t)m_einfo.errcode);
  549. info.append(String(m_einfo.errmsg, CopyString));
  550. }
  551. return true;
  552. }
  553. int PDOMySqlConnection::getAttribute(int64_t attr, Variant &value) {
  554. switch (attr) {
  555. case PDO_ATTR_CLIENT_VERSION:
  556. value = String((char *)mysql_get_client_info(), CopyString);
  557. break;
  558. case PDO_ATTR_SERVER_VERSION:
  559. value = String((char *)mysql_get_server_info(m_server), CopyString);
  560. break;
  561. case PDO_ATTR_CONNECTION_STATUS:
  562. value = String((char *)mysql_get_host_info(m_server), CopyString);
  563. break;
  564. case PDO_ATTR_SERVER_INFO: {
  565. char *tmp = (char *)mysql_stat(m_server);
  566. if (tmp) {
  567. value = String(tmp, CopyString);
  568. } else {
  569. handleError(__FILE__, __LINE__);
  570. return -1;
  571. }
  572. break;
  573. }
  574. case PDO_ATTR_AUTOCOMMIT:
  575. value = (int64_t)auto_commit;
  576. break;
  577. case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
  578. value = (int64_t)m_buffered;
  579. break;
  580. case PDO_MYSQL_ATTR_DIRECT_QUERY:
  581. value = (int64_t)m_emulate_prepare;
  582. break;
  583. case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
  584. value = (int64_t)m_max_buffer_size;
  585. break;
  586. default:
  587. return 0;
  588. }
  589. return 1;
  590. }
  591. bool PDOMySqlConnection::checkLiveness() {
  592. return !mysql_ping(m_server);
  593. }
  594. ///////////////////////////////////////////////////////////////////////////////
  595. void PDOMySqlStatement::setRowCount() {
  596. my_ulonglong count = mysql_stmt_affected_rows(m_stmt);
  597. if (count != (my_ulonglong)-1) {
  598. row_count = count;
  599. }
  600. }
  601. bool PDOMySqlStatement::executePrepared() {
  602. /* (re)bind the parameters */
  603. if (mysql_stmt_bind_param(m_stmt, m_params) || mysql_stmt_execute(m_stmt)) {
  604. if (m_params) {
  605. free(m_params);
  606. m_params = 0;
  607. }
  608. handleError(__FILE__, __LINE__);
  609. if (mysql_stmt_errno(m_stmt) == 2057) {
  610. /* CR_NEW_STMT_METADATA makes the statement unusable */
  611. m_stmt = NULL;
  612. }
  613. return false;
  614. }
  615. if (!m_result) {
  616. int i;
  617. /* figure out the result set format, if any */
  618. m_result = mysql_stmt_result_metadata(m_stmt);
  619. if (m_result) {
  620. int calc_max_length = m_conn->buffered() && m_max_length == 1;
  621. m_fields = mysql_fetch_fields(m_result);
  622. if (m_bound_result) {
  623. int i;
  624. for (i = 0; i < column_count; i++) {
  625. free(m_bound_result[i].buffer);
  626. }
  627. free(m_bound_result);
  628. free(m_out_null);
  629. free(m_out_length);
  630. }
  631. column_count = (int)mysql_num_fields(m_result);
  632. m_bound_result = (MYSQL_BIND*)calloc(column_count, sizeof(MYSQL_BIND));
  633. m_out_null = (my_bool*)calloc(column_count, sizeof(my_bool));
  634. m_out_length = (unsigned long *)calloc(column_count,
  635. sizeof(unsigned long));
  636. /* summon memory to hold the row */
  637. for (i = 0; i < column_count; i++) {
  638. if (calc_max_length && m_fields[i].type == FIELD_TYPE_BLOB) {
  639. my_bool on = 1;
  640. mysql_stmt_attr_set(m_stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &on);
  641. calc_max_length = 0;
  642. }
  643. switch (m_fields[i].type) {
  644. case FIELD_TYPE_INT24:
  645. m_bound_result[i].buffer_length = MAX_MEDIUMINT_WIDTH + 1;
  646. break;
  647. case FIELD_TYPE_LONG:
  648. m_bound_result[i].buffer_length = MAX_INT_WIDTH + 1;
  649. break;
  650. case FIELD_TYPE_LONGLONG:
  651. m_bound_result[i].buffer_length = MAX_BIGINT_WIDTH + 1;
  652. break;
  653. case FIELD_TYPE_TINY:
  654. m_bound_result[i].buffer_length = MAX_TINYINT_WIDTH + 1;
  655. break;
  656. case FIELD_TYPE_SHORT:
  657. m_bound_result[i].buffer_length = MAX_SMALLINT_WIDTH + 1;
  658. break;
  659. default:
  660. m_bound_result[i].buffer_length =
  661. m_fields[i].max_length? m_fields[i].max_length:
  662. m_fields[i].length;
  663. /* work-around for longtext and alike */
  664. if (m_bound_result[i].buffer_length > m_conn->max_buffer_size()) {
  665. m_bound_result[i].buffer_length = m_conn->max_buffer_size();
  666. }
  667. }
  668. /* there are cases where the length reported by mysql is too short.
  669. * eg: when describing a table that contains an enum column. Since
  670. * we have no way of knowing the true length either, we'll bump up
  671. * our buffer size to a reasonable size, just in case */
  672. if (m_fields[i].max_length == 0 &&
  673. m_bound_result[i].buffer_length < 128 && MYSQL_TYPE_VAR_STRING) {
  674. m_bound_result[i].buffer_length = 128;
  675. }
  676. m_out_length[i] = 0;
  677. m_bound_result[i].buffer = malloc(m_bound_result[i].buffer_length);
  678. m_bound_result[i].is_null = &m_out_null[i];
  679. m_bound_result[i].length = &m_out_length[i];
  680. m_bound_result[i].buffer_type = MYSQL_TYPE_STRING;
  681. }
  682. if (mysql_stmt_bind_result(m_stmt, m_bound_result)) {
  683. handleError(__FILE__, __LINE__);
  684. return false;
  685. }
  686. /* if buffered, pre-fetch all the data */
  687. if (m_conn->buffered()) {
  688. mysql_stmt_store_result(m_stmt);
  689. }
  690. }
  691. }
  692. setRowCount();
  693. return true;
  694. }
  695. static const char *type_to_name_native(int type) {
  696. #define PDO_MYSQL_NATIVE_TYPE_NAME(x) case FIELD_TYPE_##x: return #x;
  697. switch (type) {
  698. PDO_MYSQL_NATIVE_TYPE_NAME(STRING)
  699. PDO_MYSQL_NATIVE_TYPE_NAME(VAR_STRING)
  700. #ifdef FIELD_TYPE_TINY
  701. PDO_MYSQL_NATIVE_TYPE_NAME(TINY)
  702. #endif
  703. PDO_MYSQL_NATIVE_TYPE_NAME(SHORT)
  704. PDO_MYSQL_NATIVE_TYPE_NAME(LONG)
  705. PDO_MYSQL_NATIVE_TYPE_NAME(LONGLONG)
  706. PDO_MYSQL_NATIVE_TYPE_NAME(INT24)
  707. PDO_MYSQL_NATIVE_TYPE_NAME(FLOAT)
  708. PDO_MYSQL_NATIVE_TYPE_NAME(DOUBLE)
  709. PDO_MYSQL_NATIVE_TYPE_NAME(DECIMAL)
  710. #ifdef FIELD_TYPE_NEWDECIMAL
  711. PDO_MYSQL_NATIVE_TYPE_NAME(NEWDECIMAL)
  712. #endif
  713. #ifdef FIELD_TYPE_GEOMETRY
  714. PDO_MYSQL_NATIVE_TYPE_NAME(GEOMETRY)
  715. #endif
  716. PDO_MYSQL_NATIVE_TYPE_NAME(TIMESTAMP)
  717. #ifdef MYSQL_HAS_YEAR
  718. PDO_MYSQL_NATIVE_TYPE_NAME(YEAR)
  719. #endif
  720. PDO_MYSQL_NATIVE_TYPE_NAME(SET)
  721. PDO_MYSQL_NATIVE_TYPE_NAME(ENUM)
  722. PDO_MYSQL_NATIVE_TYPE_NAME(DATE)
  723. #ifdef FIELD_TYPE_NEWDATE
  724. PDO_MYSQL_NATIVE_TYPE_NAME(NEWDATE)
  725. #endif
  726. PDO_MYSQL_NATIVE_TYPE_NAME(TIME)
  727. PDO_MYSQL_NATIVE_TYPE_NAME(DATETIME)
  728. PDO_MYSQL_NATIVE_TYPE_NAME(TINY_BLOB)
  729. PDO_MYSQL_NATIVE_TYPE_NAME(MEDIUM_BLOB)
  730. PDO_MYSQL_NATIVE_TYPE_NAME(LONG_BLOB)
  731. PDO_MYSQL_NATIVE_TYPE_NAME(BLOB)
  732. PDO_MYSQL_NATIVE_TYPE_NAME(NULL)
  733. default:
  734. return NULL;
  735. }
  736. #undef PDO_MYSQL_NATIVE_TYPE_NAME
  737. }
  738. ///////////////////////////////////////////////////////////////////////////////
  739. PDOMySqlStatement::PDOMySqlStatement(SmartPtr<PDOMySqlResource>&& conn,
  740. MYSQL* server)
  741. : m_conn(conn->conn())
  742. , m_server(server)
  743. , m_result(nullptr)
  744. , m_fields(nullptr)
  745. , m_current_data(nullptr)
  746. , m_current_lengths(nullptr)
  747. , m_stmt(nullptr)
  748. , m_num_params(0)
  749. , m_params(nullptr)
  750. , m_in_null(nullptr)
  751. , m_in_length(nullptr)
  752. , m_bound_result(nullptr)
  753. , m_out_null(nullptr)
  754. , m_out_length(nullptr)
  755. , m_params_given(0)
  756. , m_max_length(0)
  757. {
  758. this->dbh = std::move(conn);
  759. }
  760. PDOMySqlStatement::~PDOMySqlStatement() {
  761. sweep();
  762. }
  763. void PDOMySqlStatement::sweep() {
  764. // Release the connection
  765. m_conn.reset();
  766. if (m_result) {
  767. /* free the resource */
  768. mysql_free_result(m_result);
  769. m_result = NULL;
  770. }
  771. if (m_einfo.errmsg) {
  772. free(m_einfo.errmsg);
  773. m_einfo.errmsg = NULL;
  774. }
  775. if (m_stmt) {
  776. mysql_stmt_close(m_stmt);
  777. m_stmt = NULL;
  778. }
  779. if (m_params) {
  780. free(m_params);
  781. }
  782. if (m_in_null) {
  783. free(m_in_null);
  784. }
  785. if (m_in_length) {
  786. free(m_in_length);
  787. }
  788. if (m_bound_result) {
  789. int i;
  790. for (i = 0; i < column_count; i++) {
  791. free(m_bound_result[i].buffer);
  792. }
  793. free(m_bound_result);
  794. free(m_out_null);
  795. free(m_out_length);
  796. }
  797. if (m_server) {
  798. while (mysql_more_results(m_server)) {
  799. if (mysql_next_result(m_server) != 0) {
  800. break;
  801. }
  802. MYSQL_RES *res = mysql_store_result(m_server);
  803. if (res) {
  804. mysql_free_result(res);
  805. }
  806. }
  807. }
  808. }
  809. bool PDOMySqlStatement::create(const String& sql, const Array& options) {
  810. supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
  811. String nsql;
  812. int ret = pdo_parse_params(sp_PDOStatement(this), sql, nsql);
  813. if (ret == 1) {
  814. /* query was rewritten */
  815. } else if (ret == -1) {
  816. /* failed to parse */
  817. return false;
  818. } else {
  819. nsql = sql;
  820. }
  821. if (!(m_stmt = mysql_stmt_init(m_server))) {
  822. handleError(__FILE__, __LINE__);
  823. return false;
  824. }
  825. if (mysql_stmt_prepare(m_stmt, nsql.data(), nsql.size())) {
  826. /* TODO: might need to pull statement specific info here? */
  827. /* if the query isn't supported by the protocol, fallback to emulation */
  828. if (mysql_errno(m_server) == 1295) {
  829. supports_placeholders = PDO_PLACEHOLDER_NONE;
  830. return true;
  831. }
  832. handleError(__FILE__, __LINE__);
  833. return false;
  834. }
  835. m_num_params = mysql_stmt_param_count(m_stmt);
  836. if (m_num_params) {
  837. m_params_given = 0;
  838. m_params = (MYSQL_BIND*)calloc(m_num_params, sizeof(MYSQL_BIND));
  839. m_in_null = (my_bool*)calloc(m_num_params, sizeof(my_bool));
  840. m_in_length = (unsigned long*)calloc(m_num_params, sizeof(unsigned long));
  841. }
  842. m_max_length = pdo_attr_lval(options, PDO_ATTR_MAX_COLUMN_LEN, 0);
  843. return true;
  844. }
  845. bool PDOMySqlStatement::support(SupportedMethod method) {
  846. switch (method) {
  847. case MethodSetAttribute:
  848. case MethodGetAttribute:
  849. return false;
  850. default:
  851. break;
  852. }
  853. return true;
  854. }
  855. int PDOMySqlStatement::handleError(const char *file, int line) {
  856. assert(m_conn);
  857. return m_conn->handleError(file, line, this);
  858. }
  859. bool PDOMySqlStatement::executer() {
  860. if (m_stmt) {
  861. return executePrepared();
  862. }
  863. /* ensure that we free any previous unfetched results */
  864. if (m_result) {
  865. mysql_free_result(m_result);
  866. m_result = NULL;
  867. }
  868. if (mysql_real_query(m_server, active_query_string.data(),
  869. active_query_string.size()) != 0) {
  870. handleError(__FILE__, __LINE__);
  871. return false;
  872. }
  873. my_ulonglong affected_count = mysql_affected_rows(m_server);
  874. if (affected_count == (my_ulonglong)-1) {
  875. /* we either have a query that returned a result set or an error occurred
  876. lets see if we have access to a result set */
  877. if (!m_conn->buffered()) {
  878. m_result = mysql_use_result(m_server);
  879. } else {
  880. m_result = mysql_store_result(m_server);
  881. }
  882. if (NULL == m_result) {
  883. handleError(__FILE__, __LINE__);
  884. return false;
  885. }
  886. row_count = mysql_num_rows(m_result);
  887. column_count = (int) mysql_num_fields(m_result);
  888. m_fields = mysql_fetch_fields(m_result);
  889. }
  890. else {
  891. row_count = affected_count;
  892. }
  893. return true;
  894. }
  895. bool PDOMySqlStatement::fetcher(PDOFetchOrientation ori, long offset) {
  896. int ret;
  897. if (m_stmt) {
  898. ret = mysql_stmt_fetch(m_stmt);
  899. if (ret == MYSQL_DATA_TRUNCATED) {
  900. ret = 0;
  901. }
  902. if (ret) {
  903. if (ret != MYSQL_NO_DATA) {
  904. handleError(__FILE__, __LINE__);
  905. }
  906. return false;
  907. }
  908. return true;
  909. }
  910. if (!m_result) {
  911. strcpy(error_code, "HY000");
  912. return false;
  913. }
  914. if ((m_current_data = mysql_fetch_row(m_result)) == NULL) {
  915. if (mysql_errno(m_server)) {
  916. handleError(__FILE__, __LINE__);
  917. }
  918. return false;
  919. }
  920. m_current_lengths = (long int *)mysql_fetch_lengths(m_result);
  921. return true;
  922. }
  923. bool PDOMySqlStatement::describer(int colno) {
  924. if (!m_result) {
  925. return false;
  926. }
  927. if (colno < 0 || colno >= column_count) {
  928. /* error invalid column */
  929. return false;
  930. }
  931. if (columns.empty()) {
  932. for (int i = 0; i < column_count; i++) {
  933. columns.set(i, Variant(makeSmartPtr<PDOColumn>()));
  934. }
  935. }
  936. // fetch all on demand, this seems easiest if we've been here before bail out
  937. auto col = cast<PDOColumn>(columns[0]);
  938. if (!col->name.empty()) {
  939. return true;
  940. }
  941. for (int i = 0; i < column_count; i++) {
  942. col = cast<PDOColumn>(columns[i]);
  943. if (m_conn->fetch_table_names()) {
  944. col->name = String(m_fields[i].table) + "." +
  945. String(m_fields[i].name);
  946. } else {
  947. col->name = String(m_fields[i].name, CopyString);
  948. }
  949. col->precision = m_fields[i].decimals;
  950. col->maxlen = m_fields[i].length;
  951. col->param_type = PDO_PARAM_STR;
  952. }
  953. return true;
  954. }
  955. bool PDOMySqlStatement::getColumn(int colno, Variant &value) {
  956. if (!m_result) {
  957. return false;
  958. }
  959. if (!m_stmt) {
  960. if (m_current_data == NULL || !m_result) {
  961. return false;
  962. }
  963. }
  964. if (colno < 0 || colno >= column_count) {
  965. /* error invalid column */
  966. return false;
  967. }
  968. char *ptr; int len;
  969. if (m_stmt) {
  970. if (m_out_null[colno]) {
  971. value.setNull();
  972. return true;
  973. }
  974. ptr = (char*)m_bound_result[colno].buffer;
  975. if (m_out_length[colno] > m_bound_result[colno].buffer_length) {
  976. /* mysql lied about the column width */
  977. strcpy(error_code, "01004"); /* truncated */
  978. m_out_length[colno] = m_bound_result[colno].buffer_length;
  979. len = m_out_length[colno];
  980. value = String(ptr, len, CopyString);
  981. return false;
  982. }
  983. len = m_out_length[colno];
  984. value = String(ptr, len, CopyString);
  985. return true;
  986. }
  987. ptr = m_current_data[colno];
  988. len = m_current_lengths[colno];
  989. value = String(ptr, len, CopyString);
  990. return true;
  991. }
  992. bool PDOMySqlStatement::paramHook(PDOBoundParam* param,
  993. PDOParamEvent event_type) {
  994. MYSQL_BIND *b;
  995. if (m_stmt && param->is_param) {
  996. switch (event_type) {
  997. case PDO_PARAM_EVT_ALLOC:
  998. /* sanity check parameter number range */
  999. if (param->paramno < 0 || param->paramno >= m_num_params) {
  1000. strcpy(error_code, "HY093");
  1001. return false;
  1002. }
  1003. m_params_given++;
  1004. b = &m_params[param->paramno];
  1005. param->driver_data = b;
  1006. b->is_null = &m_in_null[param->paramno];
  1007. b->length = &m_in_length[param->paramno];
  1008. /* recall how many parameters have been provided */
  1009. return true;
  1010. case PDO_PARAM_EVT_EXEC_PRE:
  1011. if ((int)m_params_given < m_num_params) {
  1012. /* too few parameter bound */
  1013. strcpy(error_code, "HY093");
  1014. return false;
  1015. }
  1016. b = (MYSQL_BIND*)param->driver_data;
  1017. *b->is_null = 0;
  1018. if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL ||
  1019. param->parameter.isNull()) {
  1020. *b->is_null = 1;
  1021. b->buffer_type = MYSQL_TYPE_STRING;
  1022. b->buffer = NULL;
  1023. b->buffer_length = 0;
  1024. *b->length = 0;
  1025. return true;
  1026. }
  1027. switch (PDO_PARAM_TYPE(param->param_type)) {
  1028. case PDO_PARAM_STMT:
  1029. return false;
  1030. case PDO_PARAM_LOB:
  1031. if (param->parameter.isResource()) {
  1032. Variant buf = HHVM_FN(stream_get_contents)(
  1033. param->parameter.toResource());
  1034. if (!same(buf, false)) {
  1035. param->parameter = buf;
  1036. } else {
  1037. pdo_raise_impl_error(dbh, this, "HY105",
  1038. "Expected a stream resource");
  1039. return false;
  1040. }
  1041. }
  1042. /* fall through */
  1043. default:
  1044. ;
  1045. }
  1046. if (param->parameter.isString()) {
  1047. String sparam = param->parameter.toString();
  1048. b->buffer_type = MYSQL_TYPE_STRING;
  1049. b->buffer = (void*)sparam.data();
  1050. b->buffer_length = sparam.size();
  1051. *b->length = sparam.size();
  1052. return true;
  1053. }
  1054. if (param->parameter.isInteger()) {
  1055. param->parameter = param->parameter.toInt64();
  1056. b->buffer_type = MYSQL_TYPE_LONG;
  1057. b->buffer = param->parameter.getInt64Data();
  1058. return true;
  1059. }
  1060. if (param->parameter.isDouble()) {
  1061. b->buffer_type = MYSQL_TYPE_DOUBLE;
  1062. b->buffer = param->parameter.getDoubleData();
  1063. return true;
  1064. }
  1065. return false;
  1066. case PDO_PARAM_EVT_FREE:
  1067. case PDO_PARAM_EVT_EXEC_POST:
  1068. case PDO_PARAM_EVT_FETCH_PRE:
  1069. case PDO_PARAM_EVT_FETCH_POST:
  1070. case PDO_PARAM_EVT_NORMALIZE:
  1071. /* do nothing */
  1072. break;
  1073. }
  1074. }
  1075. return true;
  1076. }
  1077. const StaticString
  1078. s_mysql_def("mysql:def"),
  1079. s_not_null("not_null"),
  1080. s_primary_key("primary_key"),
  1081. s_multiple_key("multiple_key"),
  1082. s_unique_key("unique_key"),
  1083. s_blob("blob"),
  1084. s_native_type("native_type"),
  1085. s_flags("flags"),
  1086. s_table("table");
  1087. bool PDOMySqlStatement::getColumnMeta(int64_t colno, Array &ret) {
  1088. if (!m_result) {
  1089. return false;
  1090. }
  1091. if (colno < 0 || colno >= column_count) {
  1092. /* error invalid column */
  1093. return false;
  1094. }
  1095. Array flags = Array::Create();
  1096. const MYSQL_FIELD *F = m_fields + colno;
  1097. if (F->def) {
  1098. ret.set(s_mysql_def, String(F->def, CopyString));
  1099. }
  1100. if (IS_NOT_NULL(F->flags)) {
  1101. flags.append(s_not_null);
  1102. }
  1103. if (IS_PRI_KEY(F->flags)) {
  1104. flags.append(s_primary_key);
  1105. }
  1106. if (F->flags & MULTIPLE_KEY_FLAG) {
  1107. flags.append(s_multiple_key);
  1108. }
  1109. if (F->flags & UNIQUE_KEY_FLAG) {
  1110. flags.append(s_unique_key);
  1111. }
  1112. if (IS_BLOB(F->flags)) {
  1113. flags.append(s_blob);
  1114. }
  1115. const char *str = type_to_name_native(F->type);
  1116. if (str) {
  1117. ret.set(s_native_type, str);
  1118. }
  1119. ret.set(s_flags, flags);
  1120. ret.set(s_table, String(F->table, CopyString));
  1121. return true;
  1122. }
  1123. bool PDOMySqlStatement::nextRowset() {
  1124. /* ensure that we free any previous unfetched results */
  1125. if (m_stmt) {
  1126. column_count = (int)mysql_num_fields(m_result);
  1127. mysql_stmt_free_result(m_stmt);
  1128. }
  1129. if (m_result) {
  1130. mysql_free_result(m_result);
  1131. m_result = NULL;
  1132. }
  1133. int ret = mysql_next_result(m_server);
  1134. if (ret > 0) {
  1135. handleError(__FILE__, __LINE__);
  1136. return false;
  1137. }
  1138. if (ret < 0) {
  1139. /* No more results */
  1140. return false;
  1141. }
  1142. my_ulonglong affected_count;
  1143. if (!m_conn->buffered()) {
  1144. m_result = mysql_use_result(m_server);
  1145. affected_count = 0;
  1146. } else {
  1147. m_result = mysql_store_result(m_server);
  1148. if ((my_ulonglong)-1 == (affected_count = mysql_affected_rows(m_server))) {
  1149. handleError(__FILE__, __LINE__);
  1150. return false;
  1151. }
  1152. }
  1153. row_count = affected_count;
  1154. if (!m_result) {
  1155. if (mysql_errno(m_server)) {
  1156. handleError(__FILE__, __LINE__);
  1157. return false;
  1158. } else {
  1159. /* DML queries */
  1160. return true;
  1161. }
  1162. }
  1163. column_count = (int)mysql_num_fields(m_result);
  1164. m_fields = mysql_fetch_fields(m_result);
  1165. return true;
  1166. }
  1167. bool PDOMySqlStatement::cursorCloser() {
  1168. if (m_result) {
  1169. mysql_free_result(m_result);
  1170. m_result = NULL;
  1171. }
  1172. if (m_stmt) {
  1173. return !mysql_stmt_free_result(m_stmt);
  1174. }
  1175. while (mysql_more_results(m_server)) {
  1176. if (mysql_next_result(m_server) != 0) {
  1177. break;
  1178. }
  1179. MYSQL_RES *res = mysql_store_result(m_server);
  1180. if (res) {
  1181. mysql_free_result(res);
  1182. }
  1183. }
  1184. return true;
  1185. }
  1186. ///////////////////////////////////////////////////////////////////////////////
  1187. PDOMySql::PDOMySql() : PDODriver("mysql") {}
  1188. SmartPtr<PDOResource> PDOMySql::createResourceImpl() {
  1189. return makeSmartPtr<PDOMySqlResource>(
  1190. std::make_shared<PDOMySqlConnection>());
  1191. }
  1192. SmartPtr<PDOResource> PDOMySql::createResourceImpl(
  1193. const sp_PDOConnection& conn
  1194. ) {
  1195. return makeSmartPtr<PDOMySqlResource>(
  1196. std::dynamic_pointer_cast<PDOMySqlConnection>(conn));
  1197. }
  1198. ///////////////////////////////////////////////////////////////////////////////
  1199. }