2 +----------------------------------------------------------------------+
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"
30 #ifdef PHP_MYSQL_UNIX_SOCK_ADDR
31 #ifdef MYSQL_UNIX_ADDR
32 #undef MYSQL_UNIX_ADDR
34 #define MYSQL_UNIX_ADDR PHP_MYSQL_UNIX_SOCK_ADDR
39 #if MYSQL_VERSION_ID >= 80004
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
) {
59 struct PDOMySqlConnection
: PDOConnection
{
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
"ed
,
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);
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
;}
129 std::shared_ptr
<PDOMySqlConnection
> m_conn
;
132 const MYSQL_FIELD
* m_fields
;
133 MYSQL_ROW m_current_data
;
134 long* m_current_lengths
;
135 PDOMySqlError m_einfo
;
138 MYSQL_BIND
* m_params
;
140 unsigned long* m_in_length
;
141 MYSQL_BIND
* m_bound_result
;
143 unsigned long* m_out_length
;
144 unsigned int m_params_given
;
145 unsigned m_max_length
:1;
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();
161 static String
pdo_attr_strval(const Array
& options
, int opt
, const char *def
) {
162 if (options
.exists(opt
)) {
163 return options
[opt
].toString();
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() {
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
) {
192 char *unix_socket
= nullptr;
193 unsigned int port
= 3306;
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 },
202 { "host", "localhost", 0 },
203 { "port", "3306", 0 },
204 { "unix_socket", MYSQL_UNIX_ADDR
, 0 },
207 #ifdef CLIENT_MULTI_RESULTS
208 |CLIENT_MULTI_RESULTS
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
;
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__
);
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
,
256 long write_timeout
= pdo_attr_lval(options
,
257 HH_PDO_MYSQL_ATTR_WRITE_TIMEOUT
,
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
,
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
,
268 m_emulate_prepare
= pdo_attr_lval(options
, PDO_ATTR_EMULATE_PREPARES
,
271 m_max_buffer_size
= pdo_attr_lval(options
, PDO_MYSQL_ATTR_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__
);
287 if (read_timeout
>= 0 && mysql_options(m_server
, MYSQL_OPT_READ_TIMEOUT
,
288 (const char *)&read_timeout
)) {
289 handleError(__FILE__
, __LINE__
);
293 if (write_timeout
>= 0 && mysql_options(m_server
, MYSQL_OPT_WRITE_TIMEOUT
,
294 (const char *)&write_timeout
)) {
295 handleError(__FILE__
, __LINE__
);
299 if (mysql_options(m_server
, MYSQL_OPT_LOCAL_INFILE
,
300 (const char *)&local_infile
)) {
301 handleError(__FILE__
, __LINE__
);
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 */
309 mysql_options(m_server
, MYSQL_OPT_RECONNECT
, (const char*)&reconnect
);
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__
);
320 default_file
= pdo_attr_strval(options
, PDO_MYSQL_ATTR_READ_DEFAULT_FILE
,
322 if (!default_file
.empty()) {
323 if (mysql_options(m_server
, MYSQL_READ_DEFAULT_FILE
,
324 default_file
.data())) {
325 handleError(__FILE__
, __LINE__
);
330 default_group
= pdo_attr_strval(options
, PDO_MYSQL_ATTR_READ_DEFAULT_GROUP
,
332 if (!default_group
.empty()) {
333 if (mysql_options(m_server
, MYSQL_READ_DEFAULT_GROUP
,
334 default_group
.data())) {
335 handleError(__FILE__
, __LINE__
);
340 compress
= pdo_attr_lval(options
, PDO_MYSQL_ATTR_COMPRESS
, 0);
342 if (mysql_options(m_server
, MYSQL_OPT_COMPRESS
, 0)) {
343 handleError(__FILE__
, __LINE__
);
348 ssl_ca
= pdo_attr_strval(options
, PDO_MYSQL_ATTR_SSL_CA
,
350 ssl_capath
= pdo_attr_strval(options
, PDO_MYSQL_ATTR_SSL_CAPATH
,
352 ssl_key
= pdo_attr_strval(options
, PDO_MYSQL_ATTR_SSL_KEY
,
354 ssl_cert
= pdo_attr_strval(options
, PDO_MYSQL_ATTR_SSL_CERT
,
356 ssl_cipher
= pdo_attr_strval(options
, PDO_MYSQL_ATTR_SSL_CIPHER
,
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__
);
375 if (mysql_options(m_server
, MYSQL_SET_CHARSET_NAME
, charset
)) {
376 handleError(__FILE__
, __LINE__
);
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
;
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__
);
400 mysql_autocommit(m_server
, auto_commit
);
405 alloc_own_columns
= 1;
406 max_escaped_char_length
= 2;
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
;
423 bool PDOMySqlConnection::support(SupportedMethod
/*method*/) {
427 bool PDOMySqlConnection::closer() {
429 mysql_close(m_server
);
432 if (m_einfo
.errmsg
) {
433 free(m_einfo
.errmsg
);
434 m_einfo
.errmsg
= NULL
;
439 int PDOMySqlConnection::handleError(const char *file
, int line
,
440 PDOMySqlStatement
* stmt
) {
441 PDOErrorType
*pdo_err
;
442 PDOMySqlError
*einfo
= &m_einfo
;
445 pdo_err
= &stmt
->error_code
;
447 pdo_err
= &error_code
;
450 if (stmt
&& stmt
->stmt()) {
451 einfo
->errcode
= mysql_stmt_errno(stmt
->stmt());
453 einfo
->errcode
= mysql_errno(m_server
);
461 einfo
->errmsg
= NULL
;
464 if (einfo
->errcode
) {
465 if (einfo
->errcode
== 2014) {
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) {
474 strdup("A stored procedure returning result sets of different size "
475 "was called. This is not supported by libmysql");
477 einfo
->errmsg
= strdup(mysql_error(m_server
));
479 } else { /* no error */
480 setPDOErrorNone(*pdo_err
);
484 if (stmt
&& stmt
->stmt()) {
485 setPDOError(*pdo_err
, mysql_stmt_sqlstate(stmt
->stmt()));
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
);
493 Array info
= Array::Create();
494 info
.append(String(*pdo_err
, CopyString
));
496 stmt
->dbh
->conn()->fetchErr(stmt
, info
);
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
);
516 if (m_emulate_prepare
) {
519 int server_version
= mysql_get_server_version(m_server
);
520 if (server_version
< 40100) {
524 if (s
->create(sql
, options
.toArray())) {
525 alloc_own_columns
= 1;
530 setPDOError(error_code
, s
->error_code
);
534 int64_t PDOMySqlConnection::doer(const String
& sql
) {
535 if (mysql_real_query(m_server
, sql
.data(), sql
.size())) {
536 handleError(__FILE__
, __LINE__
);
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
)) {
551 MYSQL_RES
*result
= mysql_store_result(m_server
);
553 mysql_free_result(result
);
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());
566 buf
[0] = buf
[len
] = '\'';
568 quoted
= s
.setSize(len
);
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
) {
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
);
594 case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY
:
595 m_buffered
= value
.toBoolean();
597 case PDO_MYSQL_ATTR_DIRECT_QUERY
:
598 case PDO_ATTR_EMULATE_PREPARES
:
599 m_emulate_prepare
= value
.toBoolean();
601 case PDO_ATTR_FETCH_TABLE_NAMES
:
602 m_fetch_table_names
= value
.toBoolean();
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;
609 m_max_buffer_size
= value
.toInt64();
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
));
629 int PDOMySqlConnection::getAttribute(int64_t attr
, Variant
&value
) {
631 case PDO_ATTR_CLIENT_VERSION
:
632 value
= String((char *)mysql_get_client_info(), CopyString
);
634 case PDO_ATTR_SERVER_VERSION
:
635 value
= String((char *)mysql_get_server_info(m_server
), CopyString
);
637 case PDO_ATTR_CONNECTION_STATUS
:
638 value
= String((char *)mysql_get_host_info(m_server
), CopyString
);
640 case PDO_ATTR_SERVER_INFO
: {
641 char *tmp
= (char *)mysql_stat(m_server
);
643 value
= String(tmp
, CopyString
);
645 handleError(__FILE__
, __LINE__
);
650 case PDO_ATTR_AUTOCOMMIT
:
651 value
= (int64_t)auto_commit
;
653 case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY
:
654 value
= (int64_t)m_buffered
;
656 case PDO_MYSQL_ATTR_DIRECT_QUERY
:
657 value
= (int64_t)m_emulate_prepare
;
659 case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE
:
660 value
= (int64_t)m_max_buffer_size
;
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) {
681 bool PDOMySqlStatement::executePrepared() {
682 /* (re)bind the parameters */
683 if (mysql_stmt_bind_param(m_stmt
, m_params
) || mysql_stmt_execute(m_stmt
)) {
688 handleError(__FILE__
, __LINE__
);
689 if (mysql_stmt_errno(m_stmt
) == 2057) {
690 /* CR_NEW_STMT_METADATA makes the statement unusable */
699 /* figure out the result set format, if any */
700 m_result
= mysql_stmt_result_metadata(m_stmt
);
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
);
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
) {
723 mysql_stmt_attr_set(m_stmt
, STMT_ATTR_UPDATE_MAX_LENGTH
, &on
);
726 switch (m_fields
[i
].type
) {
727 case FIELD_TYPE_INT24
:
728 m_bound_result
[i
].buffer_length
= MAX_MEDIUMINT_WIDTH
+ 1;
730 case FIELD_TYPE_LONG
:
731 m_bound_result
[i
].buffer_length
= MAX_INT_WIDTH
+ 1;
733 case FIELD_TYPE_LONGLONG
:
734 m_bound_result
[i
].buffer_length
= MAX_BIGINT_WIDTH
+ 1;
736 case FIELD_TYPE_TINY
:
737 m_bound_result
[i
].buffer_length
= MAX_TINYINT_WIDTH
+ 1;
739 case FIELD_TYPE_SHORT
:
740 m_bound_result
[i
].buffer_length
= MAX_SMALLINT_WIDTH
+ 1;
743 m_bound_result
[i
].buffer_length
=
744 m_fields
[i
].max_length
? m_fields
[i
].max_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;
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__
);
774 /* if buffered, pre-fetch all the data */
775 if (m_conn
->buffered()) {
776 mysql_stmt_store_result(m_stmt
);
785 static const char *type_to_name_native(int type
) {
786 #define PDO_MYSQL_NATIVE_TYPE_NAME(x) case FIELD_TYPE_##x: return #x;
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
)
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
)
804 #ifdef FIELD_TYPE_GEOMETRY
805 PDO_MYSQL_NATIVE_TYPE_NAME(GEOMETRY
)
807 PDO_MYSQL_NATIVE_TYPE_NAME(TIMESTAMP
)
808 #ifdef MYSQL_HAS_YEAR
809 PDO_MYSQL_NATIVE_TYPE_NAME(YEAR
)
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
)
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
)
827 #undef PDO_MYSQL_NATIVE_TYPE_NAME
830 ///////////////////////////////////////////////////////////////////////////////
832 PDOMySqlStatement::PDOMySqlStatement(req::ptr
<PDOMySqlResource
>&& conn
,
834 : m_conn(conn
->conn())
838 , m_current_data(nullptr)
839 , m_current_lengths(nullptr)
844 , m_in_length(nullptr)
845 , m_bound_result(nullptr)
846 , m_out_null(nullptr)
847 , m_out_length(nullptr)
851 this->dbh
= std::move(conn
);
854 PDOMySqlStatement::~PDOMySqlStatement() {
858 void PDOMySqlStatement::sweep() {
859 // Release the connection
863 /* free the resource */
864 mysql_free_result(m_result
);
867 if (m_einfo
.errmsg
) {
868 free(m_einfo
.errmsg
);
869 m_einfo
.errmsg
= NULL
;
872 mysql_stmt_close(m_stmt
);
886 if (m_bound_result
) {
888 for (i
= 0; i
< column_count
; i
++) {
889 free(m_bound_result
[i
].buffer
);
892 free(m_bound_result
);
898 while (mysql_more_results(m_server
)) {
899 if (mysql_next_result(m_server
) != 0) {
902 MYSQL_RES
*res
= mysql_store_result(m_server
);
904 mysql_free_result(res
);
910 bool PDOMySqlStatement::create(const String
& sql
, const Array
& options
) {
911 supports_placeholders
= PDO_PLACEHOLDER_POSITIONAL
;
914 int ret
= pdo_parse_params(sp_PDOStatement(this), sql
, nsql
);
916 /* query was rewritten */
917 } else if (ret
== -1) {
918 /* failed to parse */
924 if (!(m_stmt
= mysql_stmt_init(m_server
))) {
925 handleError(__FILE__
, __LINE__
);
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
;
936 handleError(__FILE__
, __LINE__
);
940 m_num_params
= mysql_stmt_param_count(m_stmt
);
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);
952 bool PDOMySqlStatement::support(SupportedMethod method
) {
954 case MethodSetAttribute
:
955 case MethodGetAttribute
:
963 int PDOMySqlStatement::handleError(const char *file
, int line
) {
965 return m_conn
->handleError(file
, line
, this);
968 bool PDOMySqlStatement::executer() {
970 return executePrepared();
973 /* ensure that we free any previous unfetched results */
975 mysql_free_result(m_result
);
979 if (mysql_real_query(m_server
, active_query_string
.data(),
980 active_query_string
.size()) != 0) {
981 handleError(__FILE__
, __LINE__
);
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
);
992 m_result
= mysql_store_result(m_server
);
994 if (NULL
== m_result
) {
995 handleError(__FILE__
, __LINE__
);
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
);
1005 row_count
= affected_count
;
1011 bool PDOMySqlStatement::fetcher(PDOFetchOrientation
/*ori*/, long /*offset*/) {
1014 ret
= mysql_stmt_fetch(m_stmt
);
1015 if (ret
== MYSQL_DATA_TRUNCATED
) {
1019 if (ret
!= MYSQL_NO_DATA
) {
1020 handleError(__FILE__
, __LINE__
);
1028 setPDOError(error_code
, "HY000");
1032 if ((m_current_data
= mysql_fetch_row(m_result
)) == NULL
) {
1033 if (mysql_errno(m_server
)) {
1034 handleError(__FILE__
, __LINE__
);
1039 m_current_lengths
= (long int *)mysql_fetch_lengths(m_result
);
1043 bool PDOMySqlStatement::describer(int colno
) {
1048 if (colno
< 0 || colno
>= column_count
) {
1049 /* error invalid column */
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()) {
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
);
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
;
1081 bool PDOMySqlStatement::getColumn(int colno
, Variant
&value
) {
1087 if (m_current_data
== NULL
|| !m_result
) {
1091 if (colno
< 0 || colno
>= column_count
) {
1092 /* error invalid column */
1097 if (m_out_null
[colno
]) {
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
);
1110 len
= m_out_length
[colno
];
1111 value
= String(ptr
, len
, CopyString
);
1114 ptr
= m_current_data
[colno
];
1115 len
= m_current_lengths
[colno
];
1116 value
= String(ptr
, len
, CopyString
);
1120 bool PDOMySqlStatement::paramHook(PDOBoundParam
* param
,
1121 PDOParamEvent event_type
) {
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");
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 */
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");
1147 b
= (MYSQL_BIND
*)param
->driver_ext_data
;
1149 if (PDO_PARAM_TYPE(param
->param_type
) == PDO_PARAM_NULL
||
1150 param
->parameter
.isNull()) {
1152 b
->buffer_type
= MYSQL_TYPE_STRING
;
1154 b
->buffer_length
= 0;
1159 switch (PDO_PARAM_TYPE(param
->param_type
)) {
1160 case PDO_PARAM_STMT
:
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
;
1169 pdo_raise_impl_error(dbh
, this, "HY105",
1170 "Expected a stream resource");
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();
1188 if (param
->parameter
.isInteger()) {
1189 param
->parameter
= param
->parameter
.toInt64();
1190 b
->buffer_type
= MYSQL_TYPE_LONG
;
1191 b
->buffer
= param
->parameter
.getInt64Data();
1194 if (param
->parameter
.isDouble()) {
1195 b
->buffer_type
= MYSQL_TYPE_DOUBLE
;
1196 b
->buffer
= param
->parameter
.getDoubleData();
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
:
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"),
1219 s_native_type("native_type"),
1223 bool PDOMySqlStatement::getColumnMeta(int64_t colno
, Array
&ret
) {
1227 if (colno
< 0 || colno
>= column_count
) {
1228 /* error invalid column */
1232 Array flags
= Array::Create();
1234 const MYSQL_FIELD
*F
= m_fields
+ colno
;
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
);
1255 ret
.set(s_native_type
, str
);
1257 ret
.set(s_flags
, flags
);
1258 ret
.set(s_table
, String(F
->table
, CopyString
));
1262 bool PDOMySqlStatement::nextRowset() {
1263 /* ensure that we free any previous unfetched results */
1265 column_count
= (int)mysql_num_fields(m_result
);
1266 mysql_stmt_free_result(m_stmt
);
1269 mysql_free_result(m_result
);
1273 int ret
= mysql_next_result(m_server
);
1275 handleError(__FILE__
, __LINE__
);
1279 /* No more results */
1283 my_ulonglong affected_count
;
1284 if (!m_conn
->buffered()) {
1285 m_result
= mysql_use_result(m_server
);
1288 m_result
= mysql_store_result(m_server
);
1289 if ((my_ulonglong
)-1 == (affected_count
= mysql_affected_rows(m_server
))) {
1290 handleError(__FILE__
, __LINE__
);
1294 row_count
= affected_count
;
1297 if (mysql_errno(m_server
)) {
1298 handleError(__FILE__
, __LINE__
);
1306 column_count
= (int)mysql_num_fields(m_result
);
1307 m_fields
= mysql_fetch_fields(m_result
);
1311 bool PDOMySqlStatement::cursorCloser() {
1313 mysql_free_result(m_result
);
1317 return !mysql_stmt_free_result(m_stmt
);
1320 while (mysql_more_results(m_server
)) {
1321 if (mysql_next_result(m_server
) != 0) {
1324 MYSQL_RES
*res
= mysql_store_result(m_server
);
1326 mysql_free_result(res
);
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 ///////////////////////////////////////////////////////////////////////////////