Fix return values for PDOMySqlConnection::commit/rollback
[hiphop-php.git] / hphp / runtime / ext / pdo_mysql / pdo_mysql.cpp
blobca64c6e45190331abb5feec5762da5ca9b61e1cf
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/pdo_mysql/pdo_mysql.h"
19 #include "hphp/runtime/ext/stream/ext_stream.h"
20 #include "hphp/runtime/base/comparisons.h"
21 #include "hphp/runtime/base/ini-setting.h"
22 #include "hphp/runtime/vm/jit/translator-inline.h"
24 #include "hphp/util/network.h"
26 #include "mysql.h"
28 #include <memory>
30 #ifdef PHP_MYSQL_UNIX_SOCK_ADDR
31 #ifdef MYSQL_UNIX_ADDR
32 #undef MYSQL_UNIX_ADDR
33 #endif
34 #define MYSQL_UNIX_ADDR PHP_MYSQL_UNIX_SOCK_ADDR
35 #endif
37 namespace HPHP {
39 #if MYSQL_VERSION_ID >= 80004
40 using my_bool = bool;
41 #endif
43 struct PDOMySqlStatement;
45 IMPLEMENT_DEFAULT_EXTENSION_VERSION(pdo_mysql, 1.0.2);
47 ///////////////////////////////////////////////////////////////////////////////
49 struct PDOMySqlError {
50 PDOMySqlError() : file(NULL), line(0), errcode(0), errmsg(NULL) {
53 const char *file;
54 int line;
55 unsigned int errcode;
56 char *errmsg;
59 struct PDOMySqlConnection : PDOConnection {
60 PDOMySqlConnection();
61 virtual ~PDOMySqlConnection();
63 bool create(const Array& options) override;
65 bool support(SupportedMethod method) override;
66 bool closer() override;
67 bool preparer(const String& sql, sp_PDOStatement *stmt,
68 const Variant& options) override;
69 int64_t doer(const String& sql) override;
70 bool quoter(const String& input, String &quoted,
71 PDOParamType paramtype) override;
72 bool begin() override;
73 bool commit() override;
74 bool rollback() override;
75 bool setAttribute(int64_t attr, const Variant& value) override;
76 String lastId(const char *name) override;
77 bool fetchErr(PDOStatement* stmt, Array &info) override;
78 int getAttribute(int64_t attr, Variant &value) override;
79 bool checkLiveness() override;
81 bool buffered() const { return m_buffered; }
82 unsigned long max_buffer_size() const { return m_max_buffer_size; }
83 bool fetch_table_names() const { return m_fetch_table_names; }
85 int handleError(const char *file, int line,
86 PDOMySqlStatement *stmt = nullptr);
88 private:
89 MYSQL* m_server;
90 unsigned m_attached : 1;
91 unsigned m_buffered : 1;
92 unsigned m_emulate_prepare : 1;
93 unsigned m_fetch_table_names : 1;
94 unsigned long m_max_buffer_size;
95 PDOMySqlError m_einfo;
98 struct PDOMySqlResource : PDOResource {
99 explicit PDOMySqlResource(std::shared_ptr<PDOMySqlConnection> conn)
100 : PDOResource(std::dynamic_pointer_cast<PDOConnection>(conn))
103 std::shared_ptr<PDOMySqlConnection> conn() const {
104 return std::dynamic_pointer_cast<PDOMySqlConnection>(m_conn);
108 struct PDOMySqlStatement : PDOStatement {
109 DECLARE_RESOURCE_ALLOCATION(PDOMySqlStatement);
111 PDOMySqlStatement(req::ptr<PDOMySqlResource>&& conn, MYSQL* server);
112 virtual ~PDOMySqlStatement();
114 bool create(const String& sql, const Array& options);
116 bool support(SupportedMethod method) override;
117 bool executer() override;
118 bool fetcher(PDOFetchOrientation ori, long offset) override;
119 bool describer(int colno) override;
120 bool getColumn(int colno, Variant &value) override;
121 bool paramHook(PDOBoundParam* param, PDOParamEvent event_type) override;
122 bool getColumnMeta(int64_t colno, Array &return_value) override;
123 bool nextRowset() override;
124 bool cursorCloser() override;
126 MYSQL_STMT *stmt() { return m_stmt;}
128 private:
129 std::shared_ptr<PDOMySqlConnection> m_conn;
130 MYSQL* m_server;
131 MYSQL_RES* m_result;
132 const MYSQL_FIELD* m_fields;
133 MYSQL_ROW m_current_data;
134 long* m_current_lengths;
135 PDOMySqlError m_einfo;
136 MYSQL_STMT* m_stmt;
137 int m_num_params;
138 MYSQL_BIND* m_params;
139 my_bool* m_in_null;
140 unsigned long* m_in_length;
141 MYSQL_BIND* m_bound_result;
142 my_bool* m_out_null;
143 unsigned long* m_out_length;
144 unsigned int m_params_given;
145 unsigned m_max_length:1;
147 void setRowCount();
148 bool executePrepared();
149 int handleError(const char* file, int line);
152 ///////////////////////////////////////////////////////////////////////////////
154 static long pdo_attr_lval(const Array& options, int opt, long defaultValue) {
155 if (options.exists(opt)) {
156 return options[opt].toInt64();
158 return defaultValue;
161 static String pdo_attr_strval(const Array& options, int opt, const char *def) {
162 if (options.exists(opt)) {
163 return options[opt].toString();
165 if (def) {
166 return def;
168 return String();
171 ///////////////////////////////////////////////////////////////////////////////
173 PDOMySqlConnection::PDOMySqlConnection()
174 : m_server(NULL), m_attached(0), m_buffered(0), m_emulate_prepare(0),
175 m_fetch_table_names(0), m_max_buffer_size(0) {
178 PDOMySqlConnection::~PDOMySqlConnection() {
179 if (m_server) {
180 mysql_close(m_server);
182 if (m_einfo.errmsg) {
183 free(m_einfo.errmsg);
187 const StaticString s_localhost("localhost");
188 const std::string s_default_socket_option("pdo_mysql.default_socket");
190 bool PDOMySqlConnection::create(const Array& options) {
191 int i, ret = 0;
192 char *unix_socket = nullptr;
193 unsigned int port = 3306;
194 char *dbname;
195 char *charset = nullptr;
196 char *default_socket = nullptr;
197 std::string default_socket_string;
199 struct pdo_data_src_parser vars[] = {
200 { "charset", nullptr, 0 },
201 { "dbname", "", 0 },
202 { "host", "localhost", 0 },
203 { "port", "3306", 0 },
204 { "unix_socket", MYSQL_UNIX_ADDR, 0 },
206 int connect_opts = 0
207 #ifdef CLIENT_MULTI_RESULTS
208 |CLIENT_MULTI_RESULTS
209 #endif
212 #ifdef CLIENT_MULTI_STATEMENTS
213 if (options.empty()) {
214 connect_opts |= CLIENT_MULTI_STATEMENTS;
215 } else if (pdo_attr_lval(options, PDO_MYSQL_ATTR_MULTI_STATEMENTS, 1)) {
216 connect_opts |= CLIENT_MULTI_STATEMENTS;
218 #endif
220 parseDataSource(data_source.data(), data_source.size(), vars, 5);
222 dbname = vars[1].optval;
224 // Extract port number from a host in case it's inlined.
225 String host(vars[2].optval, CopyString);
226 if (!host.same(s_localhost)) {
227 HostURL hosturl(host.toCppString(), port);
228 if (hosturl.isValid()) {
229 host = String(hosturl.getHost().c_str(), CopyString);
230 port = hosturl.getPort();
234 // Explicit port param overrides the
235 // implicit one from host.
236 if (vars[3].optval) {
237 port = atoi(vars[3].optval);
240 /* handle for the server */
241 if (!(m_server = mysql_init(NULL))) {
242 handleError(__FILE__, __LINE__);
243 goto cleanup;
246 m_max_buffer_size = 1024*1024;
247 m_buffered = m_emulate_prepare = 1;
248 charset = vars[0].optval;
250 /* handle MySQL options */
251 if (!options.empty()) {
252 long connect_timeout = pdo_attr_lval(options, PDO_ATTR_TIMEOUT, 30);
253 long read_timeout = pdo_attr_lval(options,
254 HH_PDO_MYSQL_ATTR_READ_TIMEOUT,
255 -1);
256 long write_timeout = pdo_attr_lval(options,
257 HH_PDO_MYSQL_ATTR_WRITE_TIMEOUT,
258 -1);
260 long local_infile = pdo_attr_lval(options, PDO_MYSQL_ATTR_LOCAL_INFILE, 0);
261 String init_cmd, default_file, default_group, ssl_ca, ssl_capath, ssl_cert,
262 ssl_key, ssl_cipher;
263 long compress = 0;
264 m_buffered = pdo_attr_lval(options, PDO_MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
266 m_emulate_prepare = pdo_attr_lval(options, PDO_MYSQL_ATTR_DIRECT_QUERY,
267 m_emulate_prepare);
268 m_emulate_prepare = pdo_attr_lval(options, PDO_ATTR_EMULATE_PREPARES,
269 m_emulate_prepare);
271 m_max_buffer_size = pdo_attr_lval(options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE,
272 m_max_buffer_size);
274 if (pdo_attr_lval(options, PDO_MYSQL_ATTR_FOUND_ROWS, 0)) {
275 connect_opts |= CLIENT_FOUND_ROWS;
277 if (pdo_attr_lval(options, PDO_MYSQL_ATTR_IGNORE_SPACE, 0)) {
278 connect_opts |= CLIENT_IGNORE_SPACE;
281 if (mysql_options(m_server, MYSQL_OPT_CONNECT_TIMEOUT,
282 (const char *)&connect_timeout)) {
283 handleError(__FILE__, __LINE__);
284 goto cleanup;
287 if (read_timeout >= 0 && mysql_options(m_server, MYSQL_OPT_READ_TIMEOUT,
288 (const char *)&read_timeout)) {
289 handleError(__FILE__, __LINE__);
290 goto cleanup;
293 if (write_timeout >= 0 && mysql_options(m_server, MYSQL_OPT_WRITE_TIMEOUT,
294 (const char *)&write_timeout)) {
295 handleError(__FILE__, __LINE__);
296 goto cleanup;
299 if (mysql_options(m_server, MYSQL_OPT_LOCAL_INFILE,
300 (const char *)&local_infile)) {
301 handleError(__FILE__, __LINE__);
302 goto cleanup;
304 #ifdef MYSQL_OPT_RECONNECT
305 /* since 5.0.3, the default for this option is 0 if not specified.
306 * we want the old behaviour */
308 long reconnect = 1;
309 mysql_options(m_server, MYSQL_OPT_RECONNECT, (const char*)&reconnect);
311 #endif
312 init_cmd = pdo_attr_strval(options, PDO_MYSQL_ATTR_INIT_COMMAND, NULL);
313 if (!init_cmd.empty()) {
314 if (mysql_options(m_server, MYSQL_INIT_COMMAND, init_cmd.data())) {
315 handleError(__FILE__, __LINE__);
316 goto cleanup;
320 default_file = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_FILE,
321 NULL);
322 if (!default_file.empty()) {
323 if (mysql_options(m_server, MYSQL_READ_DEFAULT_FILE,
324 default_file.data())) {
325 handleError(__FILE__, __LINE__);
326 goto cleanup;
330 default_group = pdo_attr_strval(options, PDO_MYSQL_ATTR_READ_DEFAULT_GROUP,
331 NULL);
332 if (!default_group.empty()) {
333 if (mysql_options(m_server, MYSQL_READ_DEFAULT_GROUP,
334 default_group.data())) {
335 handleError(__FILE__, __LINE__);
336 goto cleanup;
340 compress = pdo_attr_lval(options, PDO_MYSQL_ATTR_COMPRESS, 0);
341 if (compress) {
342 if (mysql_options(m_server, MYSQL_OPT_COMPRESS, 0)) {
343 handleError(__FILE__, __LINE__);
344 goto cleanup;
348 ssl_ca = pdo_attr_strval(options, PDO_MYSQL_ATTR_SSL_CA,
349 nullptr);
350 ssl_capath = pdo_attr_strval(options, PDO_MYSQL_ATTR_SSL_CAPATH,
351 nullptr);
352 ssl_key = pdo_attr_strval(options, PDO_MYSQL_ATTR_SSL_KEY,
353 nullptr);
354 ssl_cert = pdo_attr_strval(options, PDO_MYSQL_ATTR_SSL_CERT,
355 nullptr);
356 ssl_cipher = pdo_attr_strval(options, PDO_MYSQL_ATTR_SSL_CIPHER,
357 nullptr);
359 if ((!ssl_ca.empty() || !ssl_capath.empty() || !ssl_key.empty()
360 || !ssl_cert.empty() || !ssl_cipher.empty()) &&
361 !host.same(s_localhost)) {
362 if (mysql_ssl_set(m_server,
363 ssl_key.empty() ? nullptr : ssl_key.c_str(),
364 ssl_cert.empty() ? nullptr : ssl_cert.c_str(),
365 ssl_ca.empty() ? nullptr : ssl_ca.c_str(),
366 ssl_capath.empty() ? nullptr : ssl_capath.c_str(),
367 ssl_cipher.empty() ? nullptr : ssl_cipher.c_str())) {
368 handleError(__FILE__, __LINE__);
369 goto cleanup;
374 if (charset) {
375 if (mysql_options(m_server, MYSQL_SET_CHARSET_NAME, charset)) {
376 handleError(__FILE__, __LINE__);
377 goto cleanup;
381 if (host.empty() || host.same(s_localhost)) {
382 if (IniSetting::Get(s_default_socket_option, default_socket_string)) {
383 default_socket = new char[default_socket_string.size() + 1];
384 memcpy(default_socket, default_socket_string.c_str(),
385 default_socket_string.size() + 1);
386 unix_socket = default_socket;
387 } else {
388 unix_socket = vars[4].optval;
392 if (mysql_real_connect(m_server, host.c_str(),
393 username.c_str(), password.c_str(),
394 dbname, port, unix_socket, connect_opts) == NULL) {
395 handleError(__FILE__, __LINE__);
396 goto cleanup;
399 if (!auto_commit) {
400 mysql_autocommit(m_server, auto_commit);
403 m_attached = 1;
405 alloc_own_columns = 1;
406 max_escaped_char_length = 2;
408 ret = 1;
410 cleanup:
411 for (i = 0; i < (int)(sizeof(vars)/sizeof(vars[0])); i++) {
412 if (vars[i].freeme) {
413 free(vars[i].optval);
416 if (default_socket != nullptr) {
417 delete[] default_socket;
420 return ret;
423 bool PDOMySqlConnection::support(SupportedMethod /*method*/) {
424 return true;
427 bool PDOMySqlConnection::closer() {
428 if (m_server) {
429 mysql_close(m_server);
430 m_server = NULL;
432 if (m_einfo.errmsg) {
433 free(m_einfo.errmsg);
434 m_einfo.errmsg = NULL;
436 return false;
439 int PDOMySqlConnection::handleError(const char *file, int line,
440 PDOMySqlStatement* stmt) {
441 PDOErrorType *pdo_err;
442 PDOMySqlError *einfo = &m_einfo;
444 if (stmt) {
445 pdo_err = &stmt->error_code;
446 } else {
447 pdo_err = &error_code;
450 if (stmt && stmt->stmt()) {
451 einfo->errcode = mysql_stmt_errno(stmt->stmt());
452 } else {
453 einfo->errcode = mysql_errno(m_server);
456 einfo->file = file;
457 einfo->line = line;
459 if (einfo->errmsg) {
460 free(einfo->errmsg);
461 einfo->errmsg = NULL;
464 if (einfo->errcode) {
465 if (einfo->errcode == 2014) {
466 einfo->errmsg =
467 strdup("Cannot execute queries while other unbuffered queries are "
468 "active. Consider using PDOStatement::fetchAll(). "
469 "Alternatively, if your code is only ever going to run against "
470 "mysql, you may enable query buffering by setting the "
471 "PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.");
472 } else if (einfo->errcode == 2057) {
473 einfo->errmsg =
474 strdup("A stored procedure returning result sets of different size "
475 "was called. This is not supported by libmysql");
476 } else {
477 einfo->errmsg = strdup(mysql_error(m_server));
479 } else { /* no error */
480 setPDOErrorNone(*pdo_err);
481 return false;
484 if (stmt && stmt->stmt()) {
485 setPDOError(*pdo_err, mysql_stmt_sqlstate(stmt->stmt()));
486 } else {
487 setPDOError(*pdo_err, mysql_sqlstate(m_server));
490 if (stmt && stmt->stmt()) {
491 pdo_raise_impl_error(stmt->dbh, nullptr, pdo_err[0], einfo->errmsg);
492 } else {
493 Array info = Array::Create();
494 info.append(String(*pdo_err, CopyString));
495 if (stmt) {
496 stmt->dbh->conn()->fetchErr(stmt, info);
497 } else {
498 info.append(Variant((unsigned long) einfo->errcode));
499 info.append(String(einfo->errmsg, CopyString));
501 throw_pdo_exception(String(*pdo_err, CopyString), info,
502 "SQLSTATE[%s] [%d] %s",
503 pdo_err[0], einfo->errcode, einfo->errmsg);
505 return einfo->errcode;
508 bool PDOMySqlConnection::preparer(const String& sql, sp_PDOStatement *stmt,
509 const Variant& options) {
510 auto rsrc = req::make<PDOMySqlResource>(
511 std::dynamic_pointer_cast<PDOMySqlConnection>(shared_from_this()));
512 auto s = req::make<PDOMySqlStatement>(std::move(rsrc), m_server);
514 *stmt = s;
516 if (m_emulate_prepare) {
517 return true;
519 int server_version = mysql_get_server_version(m_server);
520 if (server_version < 40100) {
521 return true;
524 if (s->create(sql, options.toArray())) {
525 alloc_own_columns = 1;
526 return true;
529 stmt->reset();
530 setPDOError(error_code, s->error_code);
531 return false;
534 int64_t PDOMySqlConnection::doer(const String& sql) {
535 if (mysql_real_query(m_server, sql.data(), sql.size())) {
536 handleError(__FILE__, __LINE__);
537 return -1;
540 my_ulonglong c = mysql_affected_rows(m_server);
541 if (c == (my_ulonglong) -1) {
542 handleError(__FILE__, __LINE__);
543 return m_einfo.errcode ? -1 : 0;
546 /* MULTI_QUERY support - eat up all unfetched result sets */
547 while (mysql_more_results(m_server)) {
548 if (mysql_next_result(m_server)) {
549 return true;
551 MYSQL_RES *result = mysql_store_result(m_server);
552 if (result) {
553 mysql_free_result(result);
556 return c;
559 bool PDOMySqlConnection::quoter(const String& input, String& quoted,
560 PDOParamType /*paramtype*/) {
561 String s(2 * input.size() + 3, ReserveString);
562 char *buf = s.mutableData();
563 int len = mysql_real_escape_string(m_server, buf + 1,
564 input.data(), input.size());
565 len++;
566 buf[0] = buf[len] = '\'';
567 len++;
568 quoted = s.setSize(len);
569 return true;
572 bool PDOMySqlConnection::begin() {
573 return doer("START TRANSACTION") >= 0;
576 bool PDOMySqlConnection::commit() {
577 return !mysql_commit(m_server);
580 bool PDOMySqlConnection::rollback() {
581 return !mysql_rollback(m_server);
584 bool PDOMySqlConnection::setAttribute(int64_t attr, const Variant& value) {
585 switch (attr) {
586 case PDO_ATTR_AUTOCOMMIT:
587 /* ignore if the new value equals the old one */
588 if (auto_commit ^ value.toBoolean()) {
589 auto_commit = value.toBoolean();
590 mysql_autocommit(m_server, auto_commit);
592 return true;
594 case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
595 m_buffered = value.toBoolean();
596 return true;
597 case PDO_MYSQL_ATTR_DIRECT_QUERY:
598 case PDO_ATTR_EMULATE_PREPARES:
599 m_emulate_prepare = value.toBoolean();
600 return true;
601 case PDO_ATTR_FETCH_TABLE_NAMES:
602 m_fetch_table_names = value.toBoolean();
603 return true;
604 case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
605 if (value.toInt64() < 0) {
606 /* TODO: Johannes, can we throw a warning here? */
607 m_max_buffer_size = 1024*1024;
608 } else {
609 m_max_buffer_size = value.toInt64();
611 return true;
612 default:
613 return false;
617 String PDOMySqlConnection::lastId(const char* /*name*/) {
618 return (int64_t)mysql_insert_id(m_server);
621 bool PDOMySqlConnection::fetchErr(PDOStatement* /*stmt*/, Array& info) {
622 if (m_einfo.errcode) {
623 info.append((int64_t)m_einfo.errcode);
624 info.append(String(m_einfo.errmsg, CopyString));
626 return true;
629 int PDOMySqlConnection::getAttribute(int64_t attr, Variant &value) {
630 switch (attr) {
631 case PDO_ATTR_CLIENT_VERSION:
632 value = String((char *)mysql_get_client_info(), CopyString);
633 break;
634 case PDO_ATTR_SERVER_VERSION:
635 value = String((char *)mysql_get_server_info(m_server), CopyString);
636 break;
637 case PDO_ATTR_CONNECTION_STATUS:
638 value = String((char *)mysql_get_host_info(m_server), CopyString);
639 break;
640 case PDO_ATTR_SERVER_INFO: {
641 char *tmp = (char *)mysql_stat(m_server);
642 if (tmp) {
643 value = String(tmp, CopyString);
644 } else {
645 handleError(__FILE__, __LINE__);
646 return -1;
648 break;
650 case PDO_ATTR_AUTOCOMMIT:
651 value = (int64_t)auto_commit;
652 break;
653 case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
654 value = (int64_t)m_buffered;
655 break;
656 case PDO_MYSQL_ATTR_DIRECT_QUERY:
657 value = (int64_t)m_emulate_prepare;
658 break;
659 case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
660 value = (int64_t)m_max_buffer_size;
661 break;
662 default:
663 return 0;
665 return 1;
668 bool PDOMySqlConnection::checkLiveness() {
669 return !mysql_ping(m_server);
672 ///////////////////////////////////////////////////////////////////////////////
674 void PDOMySqlStatement::setRowCount() {
675 my_ulonglong count = mysql_stmt_affected_rows(m_stmt);
676 if (count != (my_ulonglong)-1) {
677 row_count = count;
681 bool PDOMySqlStatement::executePrepared() {
682 /* (re)bind the parameters */
683 if (mysql_stmt_bind_param(m_stmt, m_params) || mysql_stmt_execute(m_stmt)) {
684 if (m_params) {
685 free(m_params);
686 m_params = 0;
688 handleError(__FILE__, __LINE__);
689 if (mysql_stmt_errno(m_stmt) == 2057) {
690 /* CR_NEW_STMT_METADATA makes the statement unusable */
691 m_stmt = NULL;
693 return false;
696 if (!m_result) {
697 int i;
699 /* figure out the result set format, if any */
700 m_result = mysql_stmt_result_metadata(m_stmt);
701 if (m_result) {
702 int calc_max_length = m_conn->buffered() && m_max_length == 1;
703 m_fields = mysql_fetch_fields(m_result);
704 if (m_bound_result) {
705 for (i = 0; i < column_count; i++) {
706 free(m_bound_result[i].buffer);
708 free(m_bound_result);
709 free(m_out_null);
710 free(m_out_length);
713 column_count = (int)mysql_num_fields(m_result);
714 m_bound_result = (MYSQL_BIND*)calloc(column_count, sizeof(MYSQL_BIND));
715 m_out_null = (my_bool*)calloc(column_count, sizeof(my_bool));
716 m_out_length = (unsigned long *)calloc(column_count,
717 sizeof(unsigned long));
719 /* summon memory to hold the row */
720 for (i = 0; i < column_count; i++) {
721 if (calc_max_length && m_fields[i].type == FIELD_TYPE_BLOB) {
722 my_bool on = 1;
723 mysql_stmt_attr_set(m_stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &on);
724 calc_max_length = 0;
726 switch (m_fields[i].type) {
727 case FIELD_TYPE_INT24:
728 m_bound_result[i].buffer_length = MAX_MEDIUMINT_WIDTH + 1;
729 break;
730 case FIELD_TYPE_LONG:
731 m_bound_result[i].buffer_length = MAX_INT_WIDTH + 1;
732 break;
733 case FIELD_TYPE_LONGLONG:
734 m_bound_result[i].buffer_length = MAX_BIGINT_WIDTH + 1;
735 break;
736 case FIELD_TYPE_TINY:
737 m_bound_result[i].buffer_length = MAX_TINYINT_WIDTH + 1;
738 break;
739 case FIELD_TYPE_SHORT:
740 m_bound_result[i].buffer_length = MAX_SMALLINT_WIDTH + 1;
741 break;
742 default:
743 m_bound_result[i].buffer_length =
744 m_fields[i].max_length? m_fields[i].max_length:
745 m_fields[i].length;
746 /* work-around for longtext and alike */
747 if (m_bound_result[i].buffer_length > m_conn->max_buffer_size()) {
748 m_bound_result[i].buffer_length = m_conn->max_buffer_size();
752 /* there are cases where the length reported by mysql is too short.
753 * eg: when describing a table that contains an enum column. Since
754 * we have no way of knowing the true length either, we'll bump up
755 * our buffer size to a reasonable size, just in case */
756 if (m_fields[i].max_length == 0 &&
757 m_bound_result[i].buffer_length < 128) {
758 m_bound_result[i].buffer_length = 128;
761 m_out_length[i] = 0;
763 m_bound_result[i].buffer = malloc(m_bound_result[i].buffer_length);
764 m_bound_result[i].is_null = &m_out_null[i];
765 m_bound_result[i].length = &m_out_length[i];
766 m_bound_result[i].buffer_type = MYSQL_TYPE_STRING;
769 if (mysql_stmt_bind_result(m_stmt, m_bound_result)) {
770 handleError(__FILE__, __LINE__);
771 return false;
774 /* if buffered, pre-fetch all the data */
775 if (m_conn->buffered()) {
776 mysql_stmt_store_result(m_stmt);
781 setRowCount();
782 return true;
785 static const char *type_to_name_native(int type) {
786 #define PDO_MYSQL_NATIVE_TYPE_NAME(x) case FIELD_TYPE_##x: return #x;
788 switch (type) {
789 PDO_MYSQL_NATIVE_TYPE_NAME(STRING)
790 PDO_MYSQL_NATIVE_TYPE_NAME(VAR_STRING)
791 #ifdef FIELD_TYPE_TINY
792 PDO_MYSQL_NATIVE_TYPE_NAME(TINY)
793 #endif
794 PDO_MYSQL_NATIVE_TYPE_NAME(SHORT)
795 PDO_MYSQL_NATIVE_TYPE_NAME(LONG)
796 PDO_MYSQL_NATIVE_TYPE_NAME(LONGLONG)
797 PDO_MYSQL_NATIVE_TYPE_NAME(INT24)
798 PDO_MYSQL_NATIVE_TYPE_NAME(FLOAT)
799 PDO_MYSQL_NATIVE_TYPE_NAME(DOUBLE)
800 PDO_MYSQL_NATIVE_TYPE_NAME(DECIMAL)
801 #ifdef FIELD_TYPE_NEWDECIMAL
802 PDO_MYSQL_NATIVE_TYPE_NAME(NEWDECIMAL)
803 #endif
804 #ifdef FIELD_TYPE_GEOMETRY
805 PDO_MYSQL_NATIVE_TYPE_NAME(GEOMETRY)
806 #endif
807 PDO_MYSQL_NATIVE_TYPE_NAME(TIMESTAMP)
808 #ifdef MYSQL_HAS_YEAR
809 PDO_MYSQL_NATIVE_TYPE_NAME(YEAR)
810 #endif
811 PDO_MYSQL_NATIVE_TYPE_NAME(SET)
812 PDO_MYSQL_NATIVE_TYPE_NAME(ENUM)
813 PDO_MYSQL_NATIVE_TYPE_NAME(DATE)
814 #ifdef FIELD_TYPE_NEWDATE
815 PDO_MYSQL_NATIVE_TYPE_NAME(NEWDATE)
816 #endif
817 PDO_MYSQL_NATIVE_TYPE_NAME(TIME)
818 PDO_MYSQL_NATIVE_TYPE_NAME(DATETIME)
819 PDO_MYSQL_NATIVE_TYPE_NAME(TINY_BLOB)
820 PDO_MYSQL_NATIVE_TYPE_NAME(MEDIUM_BLOB)
821 PDO_MYSQL_NATIVE_TYPE_NAME(LONG_BLOB)
822 PDO_MYSQL_NATIVE_TYPE_NAME(BLOB)
823 PDO_MYSQL_NATIVE_TYPE_NAME(NULL)
824 default:
825 return NULL;
827 #undef PDO_MYSQL_NATIVE_TYPE_NAME
830 ///////////////////////////////////////////////////////////////////////////////
832 PDOMySqlStatement::PDOMySqlStatement(req::ptr<PDOMySqlResource>&& conn,
833 MYSQL* server)
834 : m_conn(conn->conn())
835 , m_server(server)
836 , m_result(nullptr)
837 , m_fields(nullptr)
838 , m_current_data(nullptr)
839 , m_current_lengths(nullptr)
840 , m_stmt(nullptr)
841 , m_num_params(0)
842 , m_params(nullptr)
843 , m_in_null(nullptr)
844 , m_in_length(nullptr)
845 , m_bound_result(nullptr)
846 , m_out_null(nullptr)
847 , m_out_length(nullptr)
848 , m_params_given(0)
849 , m_max_length(0)
851 this->dbh = std::move(conn);
854 PDOMySqlStatement::~PDOMySqlStatement() {
855 sweep();
858 void PDOMySqlStatement::sweep() {
859 // Release the connection
860 m_conn.reset();
862 if (m_result) {
863 /* free the resource */
864 mysql_free_result(m_result);
865 m_result = NULL;
867 if (m_einfo.errmsg) {
868 free(m_einfo.errmsg);
869 m_einfo.errmsg = NULL;
871 if (m_stmt) {
872 mysql_stmt_close(m_stmt);
873 m_stmt = NULL;
876 if (m_params) {
877 free(m_params);
879 if (m_in_null) {
880 free(m_in_null);
882 if (m_in_length) {
883 free(m_in_length);
886 if (m_bound_result) {
887 int i;
888 for (i = 0; i < column_count; i++) {
889 free(m_bound_result[i].buffer);
892 free(m_bound_result);
893 free(m_out_null);
894 free(m_out_length);
897 if (m_server) {
898 while (mysql_more_results(m_server)) {
899 if (mysql_next_result(m_server) != 0) {
900 break;
902 MYSQL_RES *res = mysql_store_result(m_server);
903 if (res) {
904 mysql_free_result(res);
910 bool PDOMySqlStatement::create(const String& sql, const Array& options) {
911 supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
913 String nsql;
914 int ret = pdo_parse_params(sp_PDOStatement(this), sql, nsql);
915 if (ret == 1) {
916 /* query was rewritten */
917 } else if (ret == -1) {
918 /* failed to parse */
919 return false;
920 } else {
921 nsql = sql;
924 if (!(m_stmt = mysql_stmt_init(m_server))) {
925 handleError(__FILE__, __LINE__);
926 return false;
929 if (mysql_stmt_prepare(m_stmt, nsql.data(), nsql.size())) {
930 /* TODO: might need to pull statement specific info here? */
931 /* if the query isn't supported by the protocol, fallback to emulation */
932 if (mysql_errno(m_server) == 1295) {
933 supports_placeholders = PDO_PLACEHOLDER_NONE;
934 return true;
936 handleError(__FILE__, __LINE__);
937 return false;
940 m_num_params = mysql_stmt_param_count(m_stmt);
941 if (m_num_params) {
942 m_params_given = 0;
943 m_params = (MYSQL_BIND*)calloc(m_num_params, sizeof(MYSQL_BIND));
944 m_in_null = (my_bool*)calloc(m_num_params, sizeof(my_bool));
945 m_in_length = (unsigned long*)calloc(m_num_params, sizeof(unsigned long));
948 m_max_length = pdo_attr_lval(options, PDO_ATTR_MAX_COLUMN_LEN, 0);
949 return true;
952 bool PDOMySqlStatement::support(SupportedMethod method) {
953 switch (method) {
954 case MethodSetAttribute:
955 case MethodGetAttribute:
956 return false;
957 default:
958 break;
960 return true;
963 int PDOMySqlStatement::handleError(const char *file, int line) {
964 assertx(m_conn);
965 return m_conn->handleError(file, line, this);
968 bool PDOMySqlStatement::executer() {
969 if (m_stmt) {
970 return executePrepared();
973 /* ensure that we free any previous unfetched results */
974 if (m_result) {
975 mysql_free_result(m_result);
976 m_result = NULL;
979 if (mysql_real_query(m_server, active_query_string.data(),
980 active_query_string.size()) != 0) {
981 handleError(__FILE__, __LINE__);
982 return false;
985 my_ulonglong affected_count = mysql_affected_rows(m_server);
986 if (affected_count == (my_ulonglong)-1) {
987 /* we either have a query that returned a result set or an error occurred
988 lets see if we have access to a result set */
989 if (!m_conn->buffered()) {
990 m_result = mysql_use_result(m_server);
991 } else {
992 m_result = mysql_store_result(m_server);
994 if (NULL == m_result) {
995 handleError(__FILE__, __LINE__);
996 return false;
999 row_count = mysql_num_rows(m_result);
1000 column_count = (int) mysql_num_fields(m_result);
1001 m_fields = mysql_fetch_fields(m_result);
1004 else {
1005 row_count = affected_count;
1008 return true;
1011 bool PDOMySqlStatement::fetcher(PDOFetchOrientation /*ori*/, long /*offset*/) {
1012 int ret;
1013 if (m_stmt) {
1014 ret = mysql_stmt_fetch(m_stmt);
1015 if (ret == MYSQL_DATA_TRUNCATED) {
1016 ret = 0;
1018 if (ret) {
1019 if (ret != MYSQL_NO_DATA) {
1020 handleError(__FILE__, __LINE__);
1022 return false;
1024 return true;
1027 if (!m_result) {
1028 setPDOError(error_code, "HY000");
1029 return false;
1032 if ((m_current_data = mysql_fetch_row(m_result)) == NULL) {
1033 if (mysql_errno(m_server)) {
1034 handleError(__FILE__, __LINE__);
1036 return false;
1039 m_current_lengths = (long int *)mysql_fetch_lengths(m_result);
1040 return true;
1043 bool PDOMySqlStatement::describer(int colno) {
1044 if (!m_result) {
1045 return false;
1048 if (colno < 0 || colno >= column_count) {
1049 /* error invalid column */
1050 return false;
1053 if (columns.empty()) {
1054 for (int i = 0; i < column_count; i++) {
1055 columns.set(i, Variant(req::make<PDOColumn>()));
1059 // fetch all on demand, this seems easiest if we've been here before bail out
1060 auto col = cast<PDOColumn>(columns[0]);
1061 if (!col->name.empty()) {
1062 return true;
1064 for (int i = 0; i < column_count; i++) {
1065 col = cast<PDOColumn>(columns[i]);
1067 if (m_conn->fetch_table_names()) {
1068 col->name = String(m_fields[i].table) + "." +
1069 String(m_fields[i].name);
1070 } else {
1071 col->name = String(m_fields[i].name, CopyString);
1074 col->precision = m_fields[i].decimals;
1075 col->maxlen = m_fields[i].length;
1076 col->param_type = PDO_PARAM_STR;
1078 return true;
1081 bool PDOMySqlStatement::getColumn(int colno, Variant &value) {
1082 if (!m_result) {
1083 return false;
1086 if (!m_stmt) {
1087 if (m_current_data == NULL || !m_result) {
1088 return false;
1091 if (colno < 0 || colno >= column_count) {
1092 /* error invalid column */
1093 return false;
1095 char *ptr; int len;
1096 if (m_stmt) {
1097 if (m_out_null[colno]) {
1098 value.setNull();
1099 return true;
1101 ptr = (char*)m_bound_result[colno].buffer;
1102 if (m_out_length[colno] > m_bound_result[colno].buffer_length) {
1103 /* mysql lied about the column width */
1104 setPDOError(error_code, "01004"); /* truncated */
1105 m_out_length[colno] = m_bound_result[colno].buffer_length;
1106 len = m_out_length[colno];
1107 value = String(ptr, len, CopyString);
1108 return false;
1110 len = m_out_length[colno];
1111 value = String(ptr, len, CopyString);
1112 return true;
1114 ptr = m_current_data[colno];
1115 len = m_current_lengths[colno];
1116 value = String(ptr, len, CopyString);
1117 return true;
1120 bool PDOMySqlStatement::paramHook(PDOBoundParam* param,
1121 PDOParamEvent event_type) {
1122 MYSQL_BIND *b;
1123 if (m_stmt && param->is_param) {
1124 switch (event_type) {
1125 case PDO_PARAM_EVT_ALLOC:
1126 /* sanity check parameter number range */
1127 if (param->paramno < 0 || param->paramno >= m_num_params) {
1128 setPDOError(error_code, "HY093");
1129 return false;
1131 m_params_given++;
1133 b = &m_params[param->paramno];
1134 param->driver_ext_data = b;
1135 b->is_null = &m_in_null[param->paramno];
1136 b->length = &m_in_length[param->paramno];
1137 /* recall how many parameters have been provided */
1138 return true;
1140 case PDO_PARAM_EVT_EXEC_PRE:
1141 if ((int)m_params_given < m_num_params) {
1142 /* too few parameter bound */
1143 setPDOError(error_code, "HY093");
1144 return false;
1147 b = (MYSQL_BIND*)param->driver_ext_data;
1148 *b->is_null = 0;
1149 if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL ||
1150 param->parameter.isNull()) {
1151 *b->is_null = 1;
1152 b->buffer_type = MYSQL_TYPE_STRING;
1153 b->buffer = NULL;
1154 b->buffer_length = 0;
1155 *b->length = 0;
1156 return true;
1159 switch (PDO_PARAM_TYPE(param->param_type)) {
1160 case PDO_PARAM_STMT:
1161 return false;
1162 case PDO_PARAM_LOB:
1163 if (param->parameter.isResource()) {
1164 Variant buf = HHVM_FN(stream_get_contents)(
1165 param->parameter.toResource());
1166 if (!same(buf, false)) {
1167 param->parameter = buf;
1168 } else {
1169 pdo_raise_impl_error(dbh, this, "HY105",
1170 "Expected a stream resource");
1171 return false;
1174 /* fall through */
1176 default:
1180 if (param->parameter.isString()) {
1181 String sparam = param->parameter.toString();
1182 b->buffer_type = MYSQL_TYPE_STRING;
1183 b->buffer = (void*)sparam.data();
1184 b->buffer_length = sparam.size();
1185 *b->length = sparam.size();
1186 return true;
1188 if (param->parameter.isInteger()) {
1189 param->parameter = param->parameter.toInt64();
1190 b->buffer_type = MYSQL_TYPE_LONG;
1191 b->buffer = param->parameter.getInt64Data();
1192 return true;
1194 if (param->parameter.isDouble()) {
1195 b->buffer_type = MYSQL_TYPE_DOUBLE;
1196 b->buffer = param->parameter.getDoubleData();
1197 return true;
1199 return false;
1200 case PDO_PARAM_EVT_FREE:
1201 case PDO_PARAM_EVT_EXEC_POST:
1202 case PDO_PARAM_EVT_FETCH_PRE:
1203 case PDO_PARAM_EVT_FETCH_POST:
1204 case PDO_PARAM_EVT_NORMALIZE:
1205 /* do nothing */
1206 break;
1209 return true;
1212 const StaticString
1213 s_mysql_def("mysql:def"),
1214 s_not_null("not_null"),
1215 s_primary_key("primary_key"),
1216 s_multiple_key("multiple_key"),
1217 s_unique_key("unique_key"),
1218 s_blob("blob"),
1219 s_native_type("native_type"),
1220 s_flags("flags"),
1221 s_table("table");
1223 bool PDOMySqlStatement::getColumnMeta(int64_t colno, Array &ret) {
1224 if (!m_result) {
1225 return false;
1227 if (colno < 0 || colno >= column_count) {
1228 /* error invalid column */
1229 return false;
1232 Array flags = Array::Create();
1234 const MYSQL_FIELD *F = m_fields + colno;
1235 if (F->def) {
1236 ret.set(s_mysql_def, String(F->def, CopyString));
1238 if (IS_NOT_NULL(F->flags)) {
1239 flags.append(s_not_null);
1241 if (IS_PRI_KEY(F->flags)) {
1242 flags.append(s_primary_key);
1244 if (F->flags & MULTIPLE_KEY_FLAG) {
1245 flags.append(s_multiple_key);
1247 if (F->flags & UNIQUE_KEY_FLAG) {
1248 flags.append(s_unique_key);
1250 if (IS_BLOB(F->flags)) {
1251 flags.append(s_blob);
1253 const char *str = type_to_name_native(F->type);
1254 if (str) {
1255 ret.set(s_native_type, str);
1257 ret.set(s_flags, flags);
1258 ret.set(s_table, String(F->table, CopyString));
1259 return true;
1262 bool PDOMySqlStatement::nextRowset() {
1263 /* ensure that we free any previous unfetched results */
1264 if (m_stmt) {
1265 column_count = (int)mysql_num_fields(m_result);
1266 mysql_stmt_free_result(m_stmt);
1268 if (m_result) {
1269 mysql_free_result(m_result);
1270 m_result = NULL;
1273 int ret = mysql_next_result(m_server);
1274 if (ret > 0) {
1275 handleError(__FILE__, __LINE__);
1276 return false;
1278 if (ret < 0) {
1279 /* No more results */
1280 return false;
1283 my_ulonglong affected_count;
1284 if (!m_conn->buffered()) {
1285 m_result = mysql_use_result(m_server);
1286 affected_count = 0;
1287 } else {
1288 m_result = mysql_store_result(m_server);
1289 if ((my_ulonglong)-1 == (affected_count = mysql_affected_rows(m_server))) {
1290 handleError(__FILE__, __LINE__);
1291 return false;
1294 row_count = affected_count;
1296 if (!m_result) {
1297 if (mysql_errno(m_server)) {
1298 handleError(__FILE__, __LINE__);
1299 return false;
1300 } else {
1301 /* DML queries */
1302 return true;
1306 column_count = (int)mysql_num_fields(m_result);
1307 m_fields = mysql_fetch_fields(m_result);
1308 return true;
1311 bool PDOMySqlStatement::cursorCloser() {
1312 if (m_result) {
1313 mysql_free_result(m_result);
1314 m_result = NULL;
1316 if (m_stmt) {
1317 return !mysql_stmt_free_result(m_stmt);
1320 while (mysql_more_results(m_server)) {
1321 if (mysql_next_result(m_server) != 0) {
1322 break;
1324 MYSQL_RES *res = mysql_store_result(m_server);
1325 if (res) {
1326 mysql_free_result(res);
1329 return true;
1332 ///////////////////////////////////////////////////////////////////////////////
1334 PDOMySql::PDOMySql() : PDODriver("mysql") {}
1336 req::ptr<PDOResource> PDOMySql::createResourceImpl() {
1337 return req::make<PDOMySqlResource>(
1338 std::make_shared<PDOMySqlConnection>());
1341 req::ptr<PDOResource> PDOMySql::createResource(
1342 const sp_PDOConnection& conn
1344 return req::make<PDOMySqlResource>(
1345 std::dynamic_pointer_cast<PDOMySqlConnection>(conn));
1348 ///////////////////////////////////////////////////////////////////////////////