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/mysql/mysql_common.h"
22 #include <unordered_set>
25 #include <folly/Conv.h>
26 #include <folly/ScopeGuard.h>
27 #include <folly/String.h>
28 #include <folly/portability/Sockets.h>
30 #include "hphp/util/network.h"
31 #include "hphp/util/text-util.h"
32 #include "hphp/util/timer.h"
34 #include "hphp/system/systemlib.h"
36 #include "hphp/runtime/base/builtin-functions.h"
37 #include "hphp/runtime/base/comparisons.h"
38 #include "hphp/runtime/base/extended-logger.h"
39 #include "hphp/runtime/base/preg.h"
40 #include "hphp/runtime/base/request-local.h"
41 #include "hphp/runtime/base/runtime-option.h"
42 #include "hphp/runtime/base/socket.h"
43 #include "hphp/runtime/base/tv-refcount.h"
44 #include "hphp/runtime/vm/jit/translator-inline.h"
46 #include "hphp/runtime/ext/mysql/ext_mysql.h"
47 #include "hphp/runtime/ext/mysql/mysql_stats.h"
48 #include "hphp/runtime/ext/pcre/ext_pcre.h"
49 #include "hphp/runtime/ext/std/ext_std_network.h"
50 #include "hphp/runtime/server/server-stats.h"
52 #include "hphp/runtime/ext/async_mysql/ext_async_mysql.h"
53 #include "hphp/runtime/vm/native-data.h"
57 using facebook::common::mysql_client::SSLOptionsProviderBase
;
59 const StaticString
s_mysqli_result("mysqli_result");
61 struct MySQLStaticInitializer
{
62 MySQLStaticInitializer() {
63 mysql_library_init(0, NULL
, NULL
);
66 static MySQLStaticInitializer s_mysql_initializer
;
68 ///////////////////////////////////////////////////////////////////////////////
70 int MySQLUtil::set_mysql_timeout(MYSQL
*mysql
,
71 MySQLUtil::TimeoutType type
,
74 // Work around a bug in webscalesql where setting a read or write timeout
75 // causes most mysql connections to fail (depending on the exact timing of
76 // packets). See https://github.com/webscalesql/webscalesql-5.6/issues/23
80 mysql_option opt
= MYSQL_OPT_CONNECT_TIMEOUT
;
81 #ifdef MYSQL_MILLISECOND_TIMEOUT
83 case MySQLUtil::ConnectTimeout
: opt
= MYSQL_OPT_CONNECT_TIMEOUT_MS
; break;
84 case MySQLUtil::ReadTimeout
: opt
= MYSQL_OPT_READ_TIMEOUT_MS
; break;
85 case MySQLUtil::WriteTimeout
: opt
= MYSQL_OPT_WRITE_TIMEOUT_MS
; break;
86 default: assert(false); break;
90 case MySQLUtil::ConnectTimeout
: opt
= MYSQL_OPT_CONNECT_TIMEOUT
; break;
91 case MySQLUtil::ReadTimeout
: opt
= MYSQL_OPT_READ_TIMEOUT
; break;
92 case MySQLUtil::WriteTimeout
: opt
= MYSQL_OPT_WRITE_TIMEOUT
; break;
93 default: assert(false); break;
95 ms
= (ms
+ 999) / 1000;
98 return mysql_options(mysql
, opt
, (const char*)&ms
);
101 ///////////////////////////////////////////////////////////////////////////////
103 void MySQLRequestData::requestInit() {
105 readTimeout
= mysqlExtension::ReadTimeout
;
109 IMPLEMENT_STATIC_REQUEST_LOCAL(MySQLRequestData
, s_mysql_data
);
111 ///////////////////////////////////////////////////////////////////////////////
112 // class MySQL statics
114 int MySQL::s_default_port
= 0;
115 bool MySQL::s_allow_reconnect
= false;
116 bool MySQL::s_allow_persistent
= true;
117 int MySQL::s_cur_num_persistent
= 0;
118 int MySQL::s_max_num_persistent
= -1;
120 std::shared_ptr
<MySQL
> MySQL::Get(const Variant
& link_identifier
) {
121 if (link_identifier
.isNull()) {
122 return GetDefaultConn();
124 auto res
= dyn_cast_or_null
<MySQLResource
>(link_identifier
);
125 return res
? res
->mysql() : nullptr;
128 MYSQL
* MySQL::GetConn(const Variant
& link_identifier
,
129 std::shared_ptr
<MySQL
>* rconn
/* = nullptr */) {
130 auto mySQL
= Get(link_identifier
);
131 MYSQL
*ret
= nullptr;
135 if (ret
== nullptr) {
136 raise_warning("supplied argument is not a valid MySQL-Link resource");
138 // Don't return a connection where mysql_real_connect() failed to most
139 // f_mysql_* APIs (the ones that deal with errno where we do want to do this
140 // anyway use MySQL::Get instead) as mysqlclient doesn't support passing
141 // connections in that state and it can crash.
142 if (mySQL
&& mySQL
->m_last_error_set
) {
150 bool MySQL::CloseConn(const Variant
& link_identifier
) {
151 auto mySQL
= Get(link_identifier
);
153 if (!mySQL
->isPersistent()) {
156 s_cur_num_persistent
--;
162 int MySQL::GetDefaultPort() {
163 if (s_default_port
<= 0) {
164 s_default_port
= MYSQL_PORT
;
165 char *env
= getenv("MYSQL_TCP_PORT");
167 s_default_port
= atoi(env
);
169 Variant ret
= HHVM_FN(getservbyname
)("mysql", "tcp");
170 if (!same(ret
, false)) {
171 s_default_port
= ret
.toInt16();
175 return s_default_port
;
178 String
MySQL::GetDefaultSocket() {
179 if (!mysqlExtension::Socket
.empty()) {
180 return mysqlExtension::Socket
;
182 return MYSQL_UNIX_ADDR
;
185 std::string
MySQL::GetHash(const String
& host
, int port
, const String
& socket
,
186 const String
& username
, const String
& password
,
189 snprintf(buf
, sizeof(buf
), "%s:%d:%s:%s:%s:%d",
190 host
.data(), port
, socket
.data(),
191 username
.data(), password
.data(), client_flags
);
192 return std::string(buf
);
196 thread_local
std::unordered_map
<std::string
,
197 std::shared_ptr
<MySQL
>> s_connections
;
200 std::shared_ptr
<MySQL
> MySQL::GetCachedImpl(const String
& host
, int port
,
201 const String
& socket
,
202 const String
& username
,
203 const String
& password
,
205 auto key
= GetHash(host
, port
, socket
, username
, password
, client_flags
);
206 return s_connections
[key
];
209 void MySQL::SetCachedImpl(const String
& host
, int port
,
210 const String
& socket
,
211 const String
& username
,
212 const String
& password
,
214 std::shared_ptr
<MySQL
> conn
) {
215 auto key
= GetHash(host
, port
, socket
, username
, password
, client_flags
);
216 s_connections
[key
] = conn
;
219 size_t MySQL::NumCachedConnections() {
220 return s_connections
.size();
223 std::shared_ptr
<MySQL
> MySQL::GetDefaultConn() {
224 if (s_mysql_data
->defaultConn
== nullptr) {
227 return s_mysql_data
->defaultConn
->mysql();
230 void MySQL::SetDefaultConn(std::shared_ptr
<MySQL
> conn
) {
231 s_mysql_data
->defaultConn
= req::make
<MySQLResource
>(std::move(conn
));
234 int MySQL::GetDefaultReadTimeout() {
235 return s_mysql_data
->readTimeout
;
238 void MySQL::SetDefaultReadTimeout(int timeout_ms
) {
239 if (timeout_ms
< 0) {
240 timeout_ms
= mysqlExtension::ReadTimeout
;
242 s_mysql_data
->readTimeout
= timeout_ms
;
245 ///////////////////////////////////////////////////////////////////////////////
250 MYSQL
* configure_conn(MYSQL
* conn
) {
251 mysql_options(conn
, MYSQL_OPT_LOCAL_INFILE
, 0);
252 if (mysqlExtension::ConnectTimeout
) {
253 MySQLUtil::set_mysql_timeout(conn
, MySQLUtil::ConnectTimeout
,
254 mysqlExtension::ConnectTimeout
);
256 int readTimeout
= MySQL::GetDefaultReadTimeout();
258 MySQLUtil::set_mysql_timeout(conn
, MySQLUtil::ReadTimeout
, readTimeout
);
259 MySQLUtil::set_mysql_timeout(conn
, MySQLUtil::WriteTimeout
, readTimeout
);
264 MYSQL
* create_new_conn() {
265 return configure_conn(mysql_init(nullptr));
270 MySQL::MySQL(const char *host
, int port
, const char *username
,
271 const char *password
, const char *database
,
272 MYSQL
* raw_connection
)
274 , m_last_error_set(false)
277 , m_multi_query(false)
278 , m_state(MySQLState::INITED
)
280 if (host
) m_host
= host
;
281 if (username
) m_username
= username
;
282 if (password
) m_password
= password
;
283 if (database
) m_database
= database
;
285 if (raw_connection
) {
286 m_conn
= configure_conn(raw_connection
);
288 m_conn
= create_new_conn();
292 void MySQL::setLastError(const char *func
) {
294 m_last_error_set
= true;
295 m_last_errno
= mysql_errno(m_conn
);
296 const char *error
= mysql_error(m_conn
);
297 m_last_error
= error
? error
: "";
298 raise_warning("%s(): %s", func
, m_last_error
.c_str());
301 void MySQL::close() {
305 m_last_error_set
= false;
308 m_last_error
.clear();
311 m_state
= MySQLState::CLOSED
;
314 bool MySQL::connect(const String
& host
, int port
, const String
& socket
,
315 const String
& username
, const String
& password
,
316 const String
& database
, int client_flags
,
317 int connect_timeout
) {
318 if (m_conn
== nullptr) {
319 m_conn
= create_new_conn();
321 if (connect_timeout
>= 0) {
322 MySQLUtil::set_mysql_timeout(m_conn
, MySQLUtil::ConnectTimeout
,
325 if (RuntimeOption::EnableStats
&& RuntimeOption::EnableSQLStats
) {
326 ServerStats::Log("sql.conn", 1);
328 IOStatusHelper
io("mysql::connect", host
.data(), port
);
330 if (m_host
.empty()) m_host
= static_cast<std::string
>(host
);
331 if (m_username
.empty()) m_username
= static_cast<std::string
>(username
);
332 if (m_password
.empty()) m_password
= static_cast<std::string
>(password
);
333 if (m_socket
.empty()) m_socket
= static_cast<std::string
>(socket
);
334 if (m_database
.empty()) m_database
= static_cast<std::string
>(database
);
335 if (!m_port
) m_port
= port
;
336 bool ret
= mysql_real_connect(m_conn
, host
.data(), username
.data(),
338 (database
.empty() ? nullptr : database
.data()),
340 socket
.empty() ? nullptr : socket
.data(),
342 if (ret
&& mysqlExtension::WaitTimeout
> 0) {
343 String
query("set session wait_timeout=");
344 query
+= String((int64_t)(mysqlExtension::WaitTimeout
/ 1000));
345 if (mysql_real_query(m_conn
, query
.data(), query
.size())) {
346 raise_notice("MySQL::connect: failed setting session wait timeout: %s",
347 mysql_error(m_conn
));
350 m_state
= (ret
) ? MySQLState::CONNECTED
: MySQLState::CLOSED
;
354 bool MySQL::reconnect(const String
& host
, int port
, const String
& socket
,
355 const String
& username
, const String
& password
,
356 const String
& database
, int client_flags
,
357 int connect_timeout
) {
359 if (m_conn
== nullptr) {
360 m_conn
= create_new_conn();
361 if (connect_timeout
>= 0) {
362 MySQLUtil::set_mysql_timeout(m_conn
, MySQLUtil::ConnectTimeout
,
365 if (RuntimeOption::EnableStats
&& RuntimeOption::EnableSQLStats
) {
366 ServerStats::Log("sql.reconn_new", 1);
368 IOStatusHelper
io("mysql::connect", host
.data(), port
);
369 ret
= mysql_real_connect(m_conn
, host
.data(), username
.data(),
371 (database
.empty() ? nullptr : database
.data()),
372 port
, socket
.data(), client_flags
);
373 } else if (m_state
== MySQLState::CONNECTED
&& !mysql_ping(m_conn
)) {
374 if (RuntimeOption::EnableStats
&& RuntimeOption::EnableSQLStats
) {
375 ServerStats::Log("sql.reconn_ok", 1);
377 if (!database
.empty()) {
378 mysql_select_db(m_conn
, database
.data());
382 if (connect_timeout
>= 0) {
383 MySQLUtil::set_mysql_timeout(m_conn
, MySQLUtil::ConnectTimeout
,
386 if (RuntimeOption::EnableStats
&& RuntimeOption::EnableSQLStats
) {
387 ServerStats::Log("sql.reconn_old", 1);
389 IOStatusHelper
io("mysql::connect", host
.data(), port
);
391 ret
= mysql_real_connect(m_conn
, host
.data(), username
.data(),
393 (database
.empty() ? nullptr : database
.data()),
394 port
, socket
.data(), client_flags
);
397 m_state
= (ret
) ? MySQLState::CONNECTED
: MySQLState::CLOSED
;
401 ///////////////////////////////////////////////////////////////////////////////
404 IMPLEMENT_RESOURCE_ALLOCATION(MySQLResource
)
406 ///////////////////////////////////////////////////////////////////////////////
411 template <typename T
>
412 req::ptr
<MySQLResult
> php_mysql_extract_result_helper(const T
& result
) {
413 auto const res
= dyn_cast_or_null
<MySQLResult
>(result
);
414 if (res
== nullptr || res
->isInvalid()) {
415 raise_warning("supplied argument is not a valid MySQL result resource");
423 req::ptr
<MySQLResult
> php_mysql_extract_result(const Resource
& result
) {
424 return php_mysql_extract_result_helper(result
);
427 req::ptr
<MySQLResult
> php_mysql_extract_result(const Variant
& result
) {
428 return php_mysql_extract_result_helper(result
);
431 const char *php_mysql_get_field_name(int field_type
) {
432 switch (field_type
) {
433 case FIELD_TYPE_STRING
:
434 case FIELD_TYPE_VAR_STRING
:
436 case FIELD_TYPE_TINY
:
437 case FIELD_TYPE_SHORT
:
438 case FIELD_TYPE_LONG
:
439 case FIELD_TYPE_LONGLONG
:
440 case FIELD_TYPE_INT24
:
442 case FIELD_TYPE_FLOAT
:
443 case FIELD_TYPE_DOUBLE
:
444 case FIELD_TYPE_DECIMAL
:
445 //case FIELD_TYPE_NEWDECIMAL:
447 case FIELD_TYPE_TIMESTAMP
:
449 case FIELD_TYPE_YEAR
:
451 case FIELD_TYPE_DATE
:
452 case FIELD_TYPE_NEWDATE
:
454 case FIELD_TYPE_TIME
:
458 case FIELD_TYPE_ENUM
:
460 case FIELD_TYPE_GEOMETRY
:
462 case FIELD_TYPE_DATETIME
:
464 case FIELD_TYPE_TINY_BLOB
:
465 case FIELD_TYPE_MEDIUM_BLOB
:
466 case FIELD_TYPE_LONG_BLOB
:
467 case FIELD_TYPE_BLOB
:
469 case FIELD_TYPE_NULL
:
477 Variant
php_mysql_field_info(const Resource
& result
, int field
,
479 auto res
= php_mysql_extract_result(result
);
480 if (!res
) return false;
482 if (!res
->seekField(field
)) return false;
484 MySQLFieldInfo
*info
;
485 if (!(info
= res
->fetchFieldInfo())) return false;
487 switch (entry_type
) {
488 case PHP_MYSQL_FIELD_NAME
:
490 case PHP_MYSQL_FIELD_TABLE
:
492 case PHP_MYSQL_FIELD_LEN
:
494 case PHP_MYSQL_FIELD_TYPE
:
495 return php_mysql_get_field_name(info
->type
);
496 case PHP_MYSQL_FIELD_FLAGS
:
500 unsigned int flags
= info
->flags
;
502 if (IS_NOT_NULL(flags
)) {
503 strcat(buf
, "not_null ");
507 if (IS_PRI_KEY(flags
)) {
508 strcat(buf
, "primary_key ");
511 #ifdef UNIQUE_KEY_FLAG
512 if (flags
& UNIQUE_KEY_FLAG
) {
513 strcat(buf
, "unique_key ");
516 #ifdef MULTIPLE_KEY_FLAG
517 if (flags
& MULTIPLE_KEY_FLAG
) {
518 strcat(buf
, "multiple_key ");
522 if (IS_BLOB(flags
)) {
523 strcat(buf
, "blob ");
527 if (flags
& UNSIGNED_FLAG
) {
528 strcat(buf
, "unsigned ");
532 if (flags
& ZEROFILL_FLAG
) {
533 strcat(buf
, "zerofill ");
537 if (flags
& BINARY_FLAG
) {
538 strcat(buf
, "binary ");
542 if (flags
& ENUM_FLAG
) {
543 strcat(buf
, "enum ");
547 if (flags
& SET_FLAG
) {
551 #ifdef AUTO_INCREMENT_FLAG
552 if (flags
& AUTO_INCREMENT_FLAG
) {
553 strcat(buf
, "auto_increment ");
556 #ifdef TIMESTAMP_FLAG
557 if (flags
& TIMESTAMP_FLAG
) {
558 strcat(buf
, "timestamp ");
561 int len
= strlen(buf
);
562 /* remove trailing space, if present */
563 if (len
&& buf
[len
-1] == ' ') {
568 return String(buf
, len
, CopyString
);
576 Variant
php_mysql_do_connect(
577 const String
& server
,
578 const String
& username
,
579 const String
& password
,
580 const String
& database
,
584 int connect_timeout_ms
,
585 int query_timeout_ms
,
586 const Array
* conn_attrs
) {
587 return php_mysql_do_connect_on_link(
601 Variant
php_mysql_do_connect_with_ssl(
602 const String
& server
,
603 const String
& username
,
604 const String
& password
,
605 const String
& database
,
607 int connect_timeout_ms
,
608 int query_timeout_ms
,
609 const Array
* conn_attrs
/* = nullptr */,
610 const Variant
& sslContextProvider
/* = null */) {
611 std::shared_ptr
<SSLOptionsProviderBase
> ssl_provider
;
612 if (!sslContextProvider
.isNull()) {
614 Native::data
<HPHP::MySSLContextProvider
>(sslContextProvider
.toObject());
615 ssl_provider
= obj
->getSSLProvider();
618 return php_mysql_do_connect_on_link(
633 static void mysql_set_ssl_options(
634 std::shared_ptr
<MySQL
> mySQL
,
635 std::shared_ptr
<SSLOptionsProviderBase
> ssl_provider
) {
636 if (!ssl_provider
|| !mySQL
|| mySQL
->get() == nullptr) {
639 ssl_provider
->setMysqlSSLOptions(mySQL
->get());
642 static void mysql_set_conn_attr(MYSQL
* mysql
, const String
& key
,
643 const String
& value
) {
645 raise_warning("MySQL: Invalid connection attribute - empty key");
647 else if (value
.empty()) {
649 std::string("MySQL: Invalid connection attribute - empty value for ") +
653 mysql_options4(mysql
, MYSQL_OPT_CONNECT_ATTR_ADD
, key
.c_str(),
658 static void mysql_set_conn_attrs(
659 std::shared_ptr
<MySQL
> mySQL
,
660 const Array
* conn_attrs
) {
661 assert(mySQL
!= nullptr && mySQL
->get() != nullptr);
663 for (auto itr
= conn_attrs
->begin(); !itr
.end(); itr
.next()) {
664 const auto& key
= itr
.first();
665 const auto& value
= itr
.secondRef();
666 if (!key
.isString()) {
668 "MySQL: Invalid connection attribute - key is not a string");
670 else if (!value
.isString()) {
672 std::string("MySQL: Invalid connection attribute - "
673 "value is not a string for key '") +
674 key
.asCStrRef().toCppString() + "'");
677 mysql_set_conn_attr(mySQL
->get(), key
.asCStrRef(), value
.asCStrRef());
683 static void mysql_store_ssl_session(
684 std::shared_ptr
<MySQL
> mySQL
,
685 std::shared_ptr
<SSLOptionsProviderBase
> ssl_provider
) {
686 if (!ssl_provider
|| !mySQL
|| mySQL
->get() == nullptr) {
689 ssl_provider
->storeMysqlSSLSession(mySQL
->get());
692 Variant
php_mysql_do_connect_on_link(
693 std::shared_ptr
<MySQL
> mySQL
,
701 int connect_timeout_ms
,
702 int query_timeout_ms
,
703 const Array
*conn_attrs
,
704 std::shared_ptr
<SSLOptionsProviderBase
> ssl_provider
) {
705 if (connect_timeout_ms
< 0) {
706 connect_timeout_ms
= mysqlExtension::ConnectTimeout
;
708 if (query_timeout_ms
< 0) {
709 query_timeout_ms
= MySQL::GetDefaultReadTimeout();
711 if (server
.empty()) server
= MySQL::GetDefaultServer();
712 if (username
.empty()) username
= MySQL::GetDefaultUsername();
713 if (password
.empty()) password
= MySQL::GetDefaultPassword();
714 if (database
.empty()) database
= MySQL::GetDefaultDatabase();
716 // server format: hostname[:port][:/path/to/socket]
717 // ipv6 hostname:port is of the form [1:2:3:4:5]:port
720 int savePersistent
= false;
722 auto slash_pos
= server
.find('/');
723 if (slash_pos
!= std::string::npos
) {
724 socket
= server
.substr(slash_pos
);
725 server
= server
.substr(0, slash_pos
- 1);
728 HostURL
hosturl(std::string(server
), MySQL::GetDefaultPort());
729 if (hosturl
.isValid()) {
730 host
= hosturl
.getHost();
731 port
= hosturl
.getPort();
734 port
= MySQL::GetDefaultPort();
737 if (socket
.empty()) {
738 socket
= MySQL::GetDefaultSocket();
741 if (MySQL::IsAllowPersistent() &&
742 MySQL::GetCurrentNumPersistent() < MySQL::GetMaxNumPersistent() &&
744 auto p_mySQL
= MySQL::GetPersistent(host
, port
, socket
, username
,
745 password
, client_flags
);
747 if (p_mySQL
!= nullptr) {
750 savePersistent
= true;
754 if (mySQL
== nullptr) {
755 mySQL
= std::make_shared
<MySQL
>(
763 // Set any connection attributes
764 if (conn_attrs
!= nullptr && conn_attrs
->size() > 0) {
765 mysql_set_conn_attrs(mySQL
, conn_attrs
);
769 mysql_set_ssl_options(mySQL
, ssl_provider
);
771 if (mySQL
->getState() == MySQLState::INITED
) {
774 if (!mySQL
->async_connect(host
, port
, socket
, username
, password
,
776 MySQL::SetDefaultConn(mySQL
); // so we can report errno by mysql_errno()
777 mySQL
->setLastError("mysql_real_connect_nonblocking_init");
781 throw_not_implemented("mysql_async_connect_start");
784 if (!mySQL
->connect(host
, port
, socket
, username
, password
,
785 database
, client_flags
, connect_timeout_ms
)) {
786 MySQL::SetDefaultConn(mySQL
); // so we can report errno by mysql_errno()
787 mySQL
->setLastError("mysql_connect");
792 if (!MySQL::IsAllowReconnect()) {
793 raise_warning("MySQL: Reconnects are not allowed");
796 if (!mySQL
->reconnect(host
, port
, socket
, username
, password
,
797 database
, client_flags
, connect_timeout_ms
)) {
798 MySQL::SetDefaultConn(mySQL
); // so we can report errno by mysql_errno()
799 mySQL
->setLastError("mysql_connect");
805 mysql_store_ssl_session(mySQL
, ssl_provider
);
807 if (savePersistent
) {
808 MySQL::SetPersistent(
809 host
, port
, socket
, username
, password
, client_flags
, mySQL
);
810 MySQL::SetCurrentNumPersistent(MySQL::GetCurrentNumPersistent() + 1);
812 MySQL::SetDefaultConn(mySQL
);
813 return Variant(req::make
<MySQLResource
>(mySQL
));
816 ///////////////////////////////////////////////////////////////////////////////
819 MySQLResult::MySQLResult(MYSQL_RES
*res
, bool localized
/* = false */)
821 , m_current_async_row(nullptr)
822 , m_localized(localized
)
823 , m_current_field(-1)
827 m_res
= nullptr; // ensure that localized results don't have another result
828 m_rows
= req::list
<req::vector
<Variant
>>(1); // sentinel
829 m_current_row
= m_rows
->begin();
835 MySQLResult::~MySQLResult() {
842 void MySQLResult::sweep() {
844 mysql_free_result(m_res
);
849 void MySQLResult::addRow() {
851 m_rows
->push_back(req::vector
<Variant
>());
852 m_rows
->back().reserve(getFieldCount());
855 void MySQLResult::addField(Variant
&& value
) {
856 m_rows
->back().push_back(std::move(value
));
859 void MySQLResult::setFieldCount(int64_t fields
) {
860 assert(m_fields
.empty());
861 m_fields
.resize(fields
);
864 void MySQLResult::setFieldInfo(int64_t f
, MYSQL_FIELD
*field
) {
865 MySQLFieldInfo
&info
= m_fields
[f
];
866 info
.name
= String(field
->name
, CopyString
);
867 info
.org_name
= String(field
->org_name
, CopyString
);
868 info
.table
= String(field
->table
, CopyString
);
869 info
.org_table
= String(field
->org_table
, CopyString
);
870 info
.def
= String(field
->def
, CopyString
);
871 info
.db
= String(field
->db
, CopyString
);
872 info
.max_length
= (int64_t)field
->max_length
;
873 info
.length
= (int64_t)field
->length
;
874 info
.type
= (int)field
->type
;
875 info
.flags
= field
->flags
;
876 info
.decimals
= field
->decimals
;
877 info
.charsetnr
= field
->charsetnr
;
880 MySQLFieldInfo
*MySQLResult::getFieldInfo(int64_t field
) {
881 if (field
< 0 || field
>= getFieldCount()) {
885 if (!m_localized
&& m_fields
.empty()) {
886 if (m_res
->fields
== NULL
) return NULL
;
888 setFieldCount(getFieldCount());
889 for (int i
= 0; i
< getFieldCount(); i
++) {
890 setFieldInfo(i
, m_res
->fields
+ i
);
893 return &m_fields
[field
];
896 Variant
MySQLResult::getField(int64_t field
) const {
897 if (!m_localized
|| field
< 0 || field
>= (int64_t)m_current_row
->size()) {
900 return (*m_current_row
)[field
];
903 int64_t MySQLResult::getFieldCount() const {
905 return (int64_t)mysql_num_fields(m_res
);
907 return m_fields
.size();
910 int64_t MySQLResult::getRowCount() const {
912 return (int64_t)mysql_num_rows(m_res
);
917 bool MySQLResult::seekRow(int64_t row
) {
918 if (row
< 0 || row
>= getRowCount()) {
920 raise_warning("Unable to jump to row %"
921 PRId64
" on MySQL result index %d",
928 mysql_data_seek(m_res
, (my_ulonglong
)row
);
930 m_current_row
= m_rows
->begin();
931 for (int i
= 0; i
< row
; i
++) m_current_row
++;
937 bool MySQLResult::fetchRow() {
938 // If not localized, use standard mysql functions on m_res
939 assert(isLocalized());
940 if (m_current_row
!= m_rows
->end()) m_current_row
++;
941 if (m_current_row
!= m_rows
->end()) {
948 bool MySQLResult::seekField(int64_t field
) {
949 if (field
< 0 || field
>= getFieldCount()) {
950 raise_warning("Field %" PRId64
" is invalid for MySQL result index %d",
956 mysql_field_seek(m_res
, (MYSQL_FIELD_OFFSET
)field
);
958 m_current_field
= field
- 1;
962 int64_t MySQLResult::tellField() {
964 return mysql_field_tell(m_res
);
966 return m_current_field
;
969 MySQLFieldInfo
*MySQLResult::fetchFieldInfo() {
971 mysql_fetch_field(m_res
);
973 if (m_current_field
< getFieldCount()) m_current_field
++;
974 return getFieldInfo(m_current_field
);
978 ///////////////////////////////////////////////////////////////////////////////
979 // MySQLStmtVariables
981 MySQLStmtVariables::MySQLStmtVariables(const Array
& arr
): m_arr(arr
) {
982 int count
= m_arr
.size();
983 m_vars
= req::calloc_raw_array
<MYSQL_BIND
>(count
);
984 m_null
= req::calloc_raw_array
<my_bool
>(count
);
985 m_length
= req::calloc_raw_array
<unsigned long>(count
);
987 for (int i
= 0; i
< count
; i
++) {
991 MYSQL_BIND
*b
= &m_vars
[i
];
992 b
->is_null
= &m_null
[i
];
993 b
->length
= &m_length
[i
];
995 b
->buffer_length
= 0;
996 b
->buffer_type
= MYSQL_TYPE_STRING
;
1000 MySQLStmtVariables::~MySQLStmtVariables() {
1001 for (int i
= 0; i
< m_arr
.size(); i
++) {
1002 auto buf
= &m_vars
[i
];
1003 if (buf
->buffer_length
> 0) {
1004 req::free(buf
->buffer
);
1010 req::free(m_length
);
1013 bool MySQLStmtVariables::bind_result(MYSQL_STMT
*stmt
) {
1014 assert(m_arr
.size() == mysql_stmt_field_count(stmt
));
1016 MYSQL_RES
*res
= mysql_stmt_result_metadata(stmt
);
1017 MYSQL_FIELD
*fields
= mysql_fetch_fields(res
);
1018 for(int i
= 0; i
< m_arr
.size(); i
++) {
1019 MYSQL_BIND
*b
= &m_vars
[i
];
1020 b
->is_unsigned
= (fields
[i
].flags
& UNSIGNED_FLAG
) ? 1 : 0;
1022 switch (fields
[i
].type
) {
1023 case MYSQL_TYPE_NULL
:
1024 b
->buffer_type
= MYSQL_TYPE_NULL
;
1025 case MYSQL_TYPE_DOUBLE
:
1026 case MYSQL_TYPE_FLOAT
:
1027 b
->buffer_type
= MYSQL_TYPE_DOUBLE
;
1028 b
->buffer_length
= sizeof(double);
1030 case MYSQL_TYPE_LONGLONG
:
1031 #if MYSQL_VERSION_ID > 50002
1032 case MYSQL_TYPE_BIT
:
1034 case MYSQL_TYPE_LONG
:
1035 case MYSQL_TYPE_INT24
:
1036 case MYSQL_TYPE_SHORT
:
1037 case MYSQL_TYPE_YEAR
:
1038 case MYSQL_TYPE_TINY
:
1039 b
->buffer_type
= MYSQL_TYPE_LONGLONG
;
1040 b
->buffer_length
= sizeof(int64_t);
1042 case MYSQL_TYPE_DATE
:
1043 case MYSQL_TYPE_NEWDATE
:
1044 case MYSQL_TYPE_DATETIME
:
1045 case MYSQL_TYPE_TIMESTAMP
:
1046 case MYSQL_TYPE_TIME
:
1047 case MYSQL_TYPE_STRING
:
1048 case MYSQL_TYPE_VARCHAR
:
1049 case MYSQL_TYPE_VAR_STRING
:
1050 case MYSQL_TYPE_ENUM
:
1051 case MYSQL_TYPE_SET
:
1052 case MYSQL_TYPE_LONG_BLOB
:
1053 case MYSQL_TYPE_MEDIUM_BLOB
:
1054 case MYSQL_TYPE_BLOB
:
1055 case MYSQL_TYPE_TINY_BLOB
:
1056 case MYSQL_TYPE_GEOMETRY
:
1057 case MYSQL_TYPE_DECIMAL
:
1058 case MYSQL_TYPE_NEWDECIMAL
:
1059 b
->buffer_type
= MYSQL_TYPE_STRING
;
1060 b
->buffer_length
= fields
[i
].max_length
?
1061 fields
[i
].max_length
:
1065 // There exists some more types in this enum like MYSQL_TYPE_TIMESTAMP2,
1066 // MYSQL_TYPE_DATETIME2, MYSQL_TYPE_TIME2 but they are just used on the
1071 if (b
->buffer_length
> 0) {
1072 b
->buffer
= req::calloc_untyped(b
->buffer_length
, 1);
1075 mysql_free_result(res
);
1077 return !mysql_stmt_bind_result(stmt
, m_vars
);
1080 void MySQLStmtVariables::update_result() {
1081 for (int i
= 0; i
< m_arr
.size(); i
++) {
1082 MYSQL_BIND
*b
= &m_vars
[i
];
1085 if (!*b
->is_null
&& b
->buffer_type
!= MYSQL_TYPE_NULL
) {
1086 switch (b
->buffer_type
) {
1087 case MYSQL_TYPE_DOUBLE
:
1088 v
= *(double*)b
->buffer
;
1090 case MYSQL_TYPE_LONGLONG
:
1091 v
= *(int64_t*)b
->buffer
;
1093 case MYSQL_TYPE_STRING
:
1094 v
= String((char *)b
->buffer
, *b
->length
, CopyString
);
1097 // We never ask for anything else than DOUBLE, LONGLONG and STRING
1098 // so in the case we get something else back something is really wrong
1103 *m_arr
.lvalAt(i
).getRefData() = v
;
1107 bool MySQLStmtVariables::init_params(MYSQL_STMT
*stmt
, const String
& types
) {
1108 assert(m_arr
.size() == types
.size());
1110 for (int i
= 0; i
< types
.size(); i
++) {
1111 MYSQL_BIND
*b
= &m_vars
[i
];
1114 b
->buffer_type
= MYSQL_TYPE_LONGLONG
;
1117 b
->buffer_type
= MYSQL_TYPE_DOUBLE
;
1120 b
->buffer_type
= MYSQL_TYPE_STRING
;
1123 b
->buffer_type
= MYSQL_TYPE_LONG_BLOB
;
1130 return !mysql_stmt_bind_param(stmt
, m_vars
);
1133 bool MySQLStmtVariables::bind_params(MYSQL_STMT
*stmt
) {
1134 m_value_arr
.clear();
1135 for (int i
= 0; i
< m_arr
.size(); i
++) {
1136 MYSQL_BIND
*b
= &m_vars
[i
];
1137 auto const& var
= m_arr
.lvalAt(i
);
1142 switch (b
->buffer_type
) {
1143 case MYSQL_TYPE_LONGLONG
:
1145 m_value_arr
.push_back(var
.toInt64());
1146 b
->buffer
= m_value_arr
.back().getInt64Data();
1149 case MYSQL_TYPE_DOUBLE
:
1151 m_value_arr
.push_back(var
.toDouble());
1152 b
->buffer
= m_value_arr
.back().getDoubleData();
1155 case MYSQL_TYPE_STRING
:
1157 m_value_arr
.push_back(var
.toString());
1158 StringData
*sd
= m_value_arr
.back().getStringData();
1159 b
->buffer
= (void *)sd
->data();
1160 // FIXME: setting buffer_length will cause the destructor to free
1161 // memory owned by the string
1162 *b
->length
= sd
->size();
1165 case MYSQL_TYPE_LONG_BLOB
:
1166 // The value are set using send_long_data so we don't have to do
1175 return !mysql_stmt_bind_param(stmt
, m_vars
);
1178 ///////////////////////////////////////////////////////////////////////////////
1181 #define VALIDATE_STMT \
1183 raise_warning("Couldn't fetch mysqli_stmt"); \
1184 return init_null(); \
1187 #define VALIDATE_PREPARED \
1189 if (!m_prepared) { \
1190 raise_warning("invalid object or resource"); \
1191 return init_null(); \
1194 MySQLStmt::MySQLStmt(MYSQL
*mysql
)
1195 : m_stmt(mysql_stmt_init(mysql
)), m_prepared(false)
1198 MySQLStmt::~MySQLStmt() {
1202 void MySQLStmt::sweep() {
1204 // Note that ~MySQLStmt is *not* going to run when we are swept.
1207 Variant
MySQLStmt::affected_rows() {
1209 return (int64_t)mysql_stmt_affected_rows(m_stmt
);
1212 Variant
MySQLStmt::attr_get(int64_t attr
) {
1217 if (mysql_stmt_attr_get(m_stmt
, (enum_stmt_attr_type
)attr
, &value
)) {
1221 #if MYSQL_VERSION_ID >= 50107
1222 if ((enum_stmt_attr_type
)attr
== STMT_ATTR_UPDATE_MAX_LENGTH
) {
1223 value
= *(my_bool
*)&value
;
1230 Variant
MySQLStmt::attr_set(int64_t attr
, int64_t value
) {
1233 #if MYSQL_VERSION_ID >= 50107
1234 if ((enum_stmt_attr_type
)attr
== STMT_ATTR_UPDATE_MAX_LENGTH
) {
1235 value
= (my_bool
)value
;
1238 return !mysql_stmt_attr_set(m_stmt
, (enum_stmt_attr_type
)attr
, &value
);
1241 Variant
MySQLStmt::bind_param(const String
& types
, const Array
& vars
) {
1244 m_param_vars
= req::make_unique
<MySQLStmtVariables
>(vars
);
1245 return m_param_vars
->init_params(m_stmt
, types
);
1248 Variant
MySQLStmt::bind_result(const Array
& vars
) {
1251 m_result_vars
= req::make_unique
<MySQLStmtVariables
>(vars
);
1252 return m_result_vars
->bind_result(m_stmt
);
1255 Variant
MySQLStmt::data_seek(int64_t offset
) {
1258 mysql_stmt_data_seek(m_stmt
, offset
);
1262 Variant
MySQLStmt::get_errno() {
1264 return (int64_t)mysql_stmt_errno(m_stmt
);
1267 Variant
MySQLStmt::get_error() {
1269 return String(mysql_stmt_error(m_stmt
), CopyString
);
1272 Variant
MySQLStmt::close() {
1275 bool ret
= !mysql_stmt_close(m_stmt
);
1283 Variant
MySQLStmt::execute() {
1287 m_param_vars
->bind_params(m_stmt
);
1290 return !mysql_stmt_execute(m_stmt
);
1293 Variant
MySQLStmt::fetch() {
1296 int64_t ret
= mysql_stmt_fetch(m_stmt
);
1298 if (ret
== MYSQL_DATA_TRUNCATED
|| ret
== MYSQL_NO_DATA
) {
1306 if (m_result_vars
) {
1307 m_result_vars
->update_result();
1313 Variant
MySQLStmt::field_count() {
1315 return (int64_t)mysql_stmt_field_count(m_stmt
);
1318 Variant
MySQLStmt::free_result() {
1320 return mysql_stmt_free_result(m_stmt
);
1323 Variant
MySQLStmt::insert_id() {
1325 return (int64_t)mysql_stmt_insert_id(m_stmt
);
1328 Variant
MySQLStmt::num_rows() {
1330 return (int64_t)mysql_stmt_num_rows(m_stmt
);
1333 Variant
MySQLStmt::param_count() {
1335 return (int64_t)mysql_stmt_param_count(m_stmt
);
1338 Variant
MySQLStmt::prepare(const String
& query
) {
1341 // Cleaning up just in case they have been set before
1342 m_param_vars
.reset();
1343 m_result_vars
.reset();
1345 m_prepared
= !mysql_stmt_prepare(m_stmt
, query
.c_str(), query
.size());
1349 Variant
MySQLStmt::reset() {
1351 return !mysql_stmt_reset(m_stmt
);
1354 Variant
MySQLStmt::result_metadata() {
1357 MYSQL_RES
*mysql_result
= mysql_stmt_result_metadata(m_stmt
);
1358 if (!mysql_result
) {
1363 args
.append(Variant(req::make
<MySQLResult
>(mysql_result
)));
1365 auto cls
= Unit::lookupClass(s_mysqli_result
.get());
1369 g_context
->invokeFunc(cls
->getCtor(), args
, obj
.get())
1374 Variant
MySQLStmt::send_long_data(int64_t param_idx
, const String
& data
) {
1376 return !mysql_stmt_send_long_data(m_stmt
, param_idx
, data
.c_str(),
1380 Variant
MySQLStmt::sqlstate() {
1382 return String(mysql_stmt_sqlstate(m_stmt
), CopyString
);
1385 Variant
MySQLStmt::store_result() {
1387 return !mysql_stmt_store_result(m_stmt
);
1390 #undef VALIDATE_STMT
1391 #undef VALIDATE_PREPARED
1393 ///////////////////////////////////////////////////////////////////////////////
1396 // Zend returns strings and NULL only, not integers or floats. We
1397 // return ints (and, sometimes, actual doubles). This behavior can be
1398 // disabled with MySQL { TypedResults = false } runtime option.
1399 Variant
mysql_makevalue(const String
& data
, MYSQL_FIELD
*mysql_field
) {
1400 return mysql_makevalue(data
, mysql_field
->type
);
1403 Variant
mysql_makevalue(const String
& data
, enum_field_types field_type
) {
1404 if (field_type
== MYSQL_TYPE_NULL
) {
1406 } else if (mysqlExtension::TypedResults
) {
1407 switch (field_type
) {
1408 case MYSQL_TYPE_DECIMAL
:
1409 case MYSQL_TYPE_TINY
:
1410 case MYSQL_TYPE_SHORT
:
1411 case MYSQL_TYPE_LONG
:
1412 case MYSQL_TYPE_LONGLONG
:
1413 case MYSQL_TYPE_INT24
:
1414 case MYSQL_TYPE_YEAR
:
1415 return data
.toInt64();
1416 case MYSQL_TYPE_FLOAT
:
1417 case MYSQL_TYPE_DOUBLE
:
1418 //case MYSQL_TYPE_NEWDECIMAL:
1419 return data
.toDouble();
1430 unsigned long cli_safe_read(MYSQL
*);
1431 unsigned long net_field_length(unsigned char **);
1432 void free_root(::MEM_ROOT
*, int);
1435 static bool php_mysql_read_rows(MYSQL
*mysql
, const Variant
& result
) {
1436 unsigned long pkt_len
;
1438 unsigned int fields
= mysql
->field_count
;
1439 NET
*net
= &mysql
->net
;
1440 auto res
= php_mysql_extract_result(result
);
1442 if ((pkt_len
= cli_safe_read(mysql
)) == packet_error
) {
1446 res
->setFieldCount((int64_t)fields
);
1448 // localizes all the rows
1449 while (*(cp
= net
->read_pos
) != 254 || pkt_len
>= 8) {
1451 for (unsigned int i
= 0; i
< fields
; i
++) {
1452 unsigned long len
= net_field_length(&cp
);
1454 if (len
!= NULL_LENGTH
) {
1455 data
= mysql_makevalue(String((char *)cp
, len
, CopyString
),
1458 if (mysql
->fields
) {
1459 if (mysql
->fields
[i
].max_length
< len
)
1460 mysql
->fields
[i
].max_length
= len
;
1463 res
->addField(std::move(data
));
1465 if ((pkt_len
= cli_safe_read(mysql
)) == packet_error
) {
1470 // localizes all the field info
1471 for (unsigned int i
= 0; i
< fields
; i
++) {
1472 res
->setFieldInfo((int64_t)i
, mysql
->fields
+ i
);
1478 static Variant
php_mysql_localize_result(MYSQL
*mysql
) {
1479 #if MYSQL_VERSION_ID <= 50138
1480 mysql
= mysql
->last_used_con
;
1483 if (!mysql
->fields
) return true;
1484 if (mysql
->status
!= MYSQL_STATUS_GET_RESULT
) {
1485 // consistent with php_mysql_do_query_and_get_result
1488 mysql
->status
= MYSQL_STATUS_READY
;
1489 Variant
result(req::make
<MySQLResult
>(nullptr, true));
1490 if (!php_mysql_read_rows(mysql
, result
)) {
1495 if (mysql
->fields
) {
1496 free_root(&mysql
->field_alloc
, 0);
1498 mysql
->unbuffered_fetch_owner
= 0;
1504 MySQLQueryReturn
php_mysql_do_query(const String
& query
, const Variant
& link_id
,
1506 SYNC_VM_REGS_SCOPED();
1507 if (mysqlExtension::ReadOnly
&&
1508 same(preg_match("/^((\\/\\*.*?\\*\\/)|\\(|\\s)*select/i", query
),
1510 raise_notice("runtime/ext_mysql: write query not executed [%s]",
1512 return MySQLQueryReturn::OK
; // pretend it worked
1515 std::shared_ptr
<MySQL
> rconn
= nullptr;
1516 MYSQL
* conn
= MySQL::GetConn(link_id
, &rconn
);
1517 if (!conn
|| !rconn
) return MySQLQueryReturn::FAIL
;
1519 if (RuntimeOption::EnableStats
&& RuntimeOption::EnableSQLStats
) {
1520 ServerStats::Log("sql.query", 1);
1522 // removing comments, which can be wrong actually if some string field's
1523 // value has /* or */ in it.
1525 String q
= preg_replace(result
, "/\\/\\*.*?\\*\\//", " ", query
) ?
1526 result
.toString() : query
;
1529 preg_match("/^(?:\\(|\\s)*(?:"
1530 "(insert).*?\\s+(?:into\\s+)?([^\\s\\(,]+)|"
1531 "(update|set|show)\\s+([^\\s\\(,]+)|"
1532 "(replace).*?\\s+into\\s+([^\\s\\(,]+)|"
1533 "(delete).*?\\s+from\\s+([^\\s\\(,]+)|"
1534 "(select).*?[\\s`]+from\\s+([^\\s\\(,]+)|"
1535 "(create|alter|drop).*?\\s+table\\s+([^\\s\\(,]+))/is",
1537 auto marray
= matches
.toArray();
1538 int size
= marray
.size();
1540 auto verb
= toLower(marray
[size
- 2].toString().slice());
1541 auto table
= toLower(marray
[size
- 1].toString().slice());
1542 if (!table
.empty() && table
[0] == '`') {
1543 table
= table
.substr(1, table
.length() - 2);
1545 ServerStats::Log(std::string("sql.query.") + table
+ "." + verb
, 1);
1546 if (RuntimeOption::EnableStats
&& RuntimeOption::EnableSQLTableStats
) {
1547 MySqlStats::Record(verb
, rconn
->m_xaction_count
, table
);
1548 if (verb
== "update") {
1549 preg_match("/([^\\s,]+)\\s*=\\s*([^\\s,]+)[\\+\\-]/",
1551 marray
= matches
.toArray();
1552 size
= marray
.size();
1553 if (size
> 2 && same(marray
[1], marray
[2])) {
1554 MySqlStats::Record("incdec", rconn
->m_xaction_count
, table
);
1557 // we only bump it up when we're in the middle of a transaction
1558 if (rconn
->m_xaction_count
) {
1559 ++rconn
->m_xaction_count
;
1563 preg_match("/^(?:(?:\\/\\*.*?\\*\\/)|\\(|\\s)*"
1564 "(start transaction|begin|commit|rollback|select)/is",
1566 auto marray
= matches
.toArray();
1567 size
= marray
.size();
1569 auto verb
= toLower(marray
[1].toString().slice());
1570 rconn
->m_xaction_count
= ((verb
== "begin" ||
1571 verb
== "start transaction") ? 1 : 0);
1572 ServerStats::Log(std::string("sql.query.") + verb
, 1);
1573 if (RuntimeOption::EnableStats
&& RuntimeOption::EnableSQLTableStats
) {
1574 MySqlStats::Record(verb
);
1577 raise_warning("Unable to record MySQL stats with: %s", query
.data());
1578 ServerStats::Log("sql.query.unknown", 1);
1583 SlowTimer
timer(mysqlExtension::SlowQueryThreshold
,
1584 "runtime/ext_mysql: slow query", query
.data());
1585 IOStatusHelper
io("mysql::query", rconn
->m_host
.c_str(), rconn
->m_port
);
1586 unsigned long tid
= mysql_thread_id(conn
);
1588 // disable explicitly
1589 auto mySQL
= MySQL::Get(link_id
);
1591 raise_warning("supplied argument is not a valid MySQL-Link resource");
1592 return MySQLQueryReturn::FAIL
;
1595 if (mySQL
->m_multi_query
&& !mysql_set_server_option(conn
, MYSQL_OPTION_MULTI_STATEMENTS_OFF
)) {
1596 mySQL
->m_multi_query
= false;
1601 mySQL
->m_async_query
= query
.toCppString();
1602 return MySQLQueryReturn::OK
;
1604 throw_not_implemented("mysql_async_query_start");
1608 if (mysql_real_query(conn
, query
.data(), query
.size())) {
1609 #ifdef HHVM_MYSQL_TRACE_MODE
1610 if (RuntimeOption::EnableHipHopSyntax
) {
1611 raise_notice("runtime/ext_mysql: failed executing [%s] [%s]",
1612 query
.data(), mysql_error(conn
));
1616 // When we are timed out, and we're SELECT-ing, we're potentially
1617 // running a long query on the server without waiting for any results
1618 // back, wasting server resource. So we're sending a KILL command
1619 // to see if we can stop the query execution.
1620 if (tid
&& mysqlExtension::KillOnTimeout
) {
1621 unsigned int errcode
= mysql_errno(conn
);
1622 if (errcode
== 2058 /* CR_NET_READ_INTERRUPTED */ ||
1623 errcode
== 2059 /* CR_NET_WRITE_INTERRUPTED */) {
1625 preg_match("/^((\\/\\*.*?\\*\\/)|\\(|\\s)*select/is", query
);
1626 if (!same(ret
, false)) {
1627 MYSQL
*new_conn
= create_new_conn();
1628 IOStatusHelper
io2("mysql::kill", rconn
->m_host
.c_str(),
1630 MYSQL
*connected
= mysql_real_connect
1631 (new_conn
, rconn
->m_host
.c_str(), rconn
->m_username
.c_str(),
1632 rconn
->m_password
.c_str(), nullptr, rconn
->m_port
, nullptr, 0);
1634 std::string killsql
= "KILL " + folly::to
<std::string
>(tid
);
1635 if (mysql_real_query(connected
, killsql
.c_str(), killsql
.size())) {
1636 raise_warning("Unable to kill thread %lu", tid
);
1639 mysql_close(new_conn
);
1644 return MySQLQueryReturn::FAIL
;
1646 Logger::Verbose("runtime/ext_mysql: successfully executed [%dms] [%s]",
1647 (int)timer
.getTime(), query
.data());
1648 if (mysql_field_count(conn
) == 0) {
1649 return MySQLQueryReturn::OK
;
1651 return MySQLQueryReturn::OK_FETCH_RESULT
;
1655 Variant
php_mysql_get_result(const Variant
& link_id
, bool use_store
) {
1656 std::shared_ptr
<MySQL
> rconn
= nullptr;
1657 MYSQL
*conn
= MySQL::GetConn(link_id
, &rconn
);
1658 if (!conn
|| !rconn
) return false;
1660 MYSQL_RES
*mysql_result
;
1663 // Facebook specific optimization which depends
1664 // on versions of MySQL which allow access to the
1665 // grotty internals of libmysqlclient
1667 // If php_mysql_localize_result ever gets rewritten
1668 // to use standard APIs, this can be opened up to everyone.
1669 if (mysqlExtension::Localize
) {
1670 return php_mysql_localize_result(conn
);
1673 mysql_result
= mysql_store_result(conn
);
1675 mysql_result
= mysql_use_result(conn
);
1677 if (!mysql_result
) {
1678 if (mysql_field_count(conn
) > 0) {
1679 raise_warning("Unable to save result set");
1685 auto r
= req::make
<MySQLResult
>(mysql_result
);
1687 if (RuntimeOption::MaxSQLRowCount
> 0 &&
1688 (s_mysql_data
->totalRowCount
+= r
->getRowCount())
1689 > RuntimeOption::MaxSQLRowCount
) {
1690 ExtendedLogger::Error(
1691 "MaxSQLRowCount is over: fetching at least %d rows",
1692 s_mysql_data
->totalRowCount
1694 s_mysql_data
->totalRowCount
= 0; // so no repetitive logging
1697 return Variant(std::move(r
));
1700 Variant
php_mysql_do_query_and_get_result(const String
& query
, const Variant
& link_id
,
1701 bool use_store
, bool async_mode
) {
1702 MySQLQueryReturn result
= php_mysql_do_query(query
, link_id
, async_mode
);
1705 case MySQLQueryReturn::OK_FETCH_RESULT
:
1706 return php_mysql_get_result(link_id
, use_store
);
1707 case MySQLQueryReturn::OK
:
1709 case MySQLQueryReturn::FAIL
:
1716 ///////////////////////////////////////////////////////////////////////////////
1719 Variant
php_mysql_fetch_hash(const Resource
& result
, int result_type
) {
1720 if ((result_type
& PHP_MYSQL_BOTH
) == 0) {
1721 throw_invalid_argument("result_type: %d", result_type
);
1725 auto res
= php_mysql_extract_result(result
);
1726 if (!res
) return false;
1729 if (res
->isLocalized()) {
1730 if (!res
->fetchRow()) return false;
1732 for (int i
= 0; i
< res
->getFieldCount(); i
++) {
1733 if (result_type
& PHP_MYSQL_NUM
) {
1734 ret
.set(i
, res
->getField(i
));
1736 if (result_type
& PHP_MYSQL_ASSOC
) {
1737 MySQLFieldInfo
*info
= res
->getFieldInfo(i
);
1738 ret
.set(info
->name
, res
->getField(i
));
1744 MYSQL_RES
*mysql_result
= res
->get();
1745 MYSQL_ROW mysql_row
= mysql_fetch_row(mysql_result
);
1749 unsigned long *mysql_row_lengths
= mysql_fetch_lengths(mysql_result
);
1750 if (!mysql_row_lengths
) {
1754 mysql_field_seek(mysql_result
, 0);
1756 MYSQL_FIELD
*mysql_field
;
1758 for (mysql_field
= mysql_fetch_field(mysql_result
), i
= 0; mysql_field
;
1759 mysql_field
= mysql_fetch_field(mysql_result
), i
++) {
1762 data
= mysql_makevalue(String(mysql_row
[i
], mysql_row_lengths
[i
],
1763 CopyString
), mysql_field
);
1765 if (result_type
& PHP_MYSQL_NUM
) {
1768 if (result_type
& PHP_MYSQL_ASSOC
) {
1769 ret
.set(String(mysql_field
->name
, CopyString
), data
);
1775 /* The mysql_*_nonblocking calls are Facebook extensions to
1776 libmysqlclient; for now, protect with an ifdef. Once open sourced,
1777 the client will be detectable via its own ifdef. */
1780 const int64_t k_ASYNC_OP_INVALID
= 0;
1781 const int64_t k_ASYNC_OP_UNSET
= ASYNC_OP_UNSET
;
1782 const int64_t k_ASYNC_OP_CONNECT
= ASYNC_OP_CONNECT
;
1783 const int64_t k_ASYNC_OP_QUERY
= ASYNC_OP_QUERY
;
1785 bool MySQL::async_connect(const String
& host
, int port
, const String
& socket
,
1786 const String
& username
, const String
& password
,
1787 const String
& database
) {
1788 if (m_conn
== nullptr) {
1789 m_conn
= create_new_conn();
1791 if (RuntimeOption::EnableStats
&& RuntimeOption::EnableSQLStats
) {
1792 ServerStats::Log("sql.conn", 1);
1794 IOStatusHelper
io("mysql::async_connect", host
.data(), port
);
1795 m_xaction_count
= 0;
1796 m_host
= static_cast<std::string
>(host
);
1797 m_username
= static_cast<std::string
>(username
);
1798 m_password
= static_cast<std::string
>(password
);
1799 m_socket
= static_cast<std::string
>(socket
);
1800 m_database
= static_cast<std::string
>(database
);
1801 bool ret
= mysql_real_connect_nonblocking_init(
1802 m_conn
, m_host
.c_str(), m_username
.c_str(), m_password
.c_str(),
1803 (m_database
.empty() ? nullptr : m_database
.c_str()), port
,
1804 m_socket
.empty() ? nullptr : m_socket
.c_str(),
1805 CLIENT_INTERACTIVE
);
1807 m_state
= (ret
) ? MySQLState::CONNECTED
: MySQLState::CLOSED
;
1813 // Bogus values for non-facebook libmysqlclients.
1814 const int64_t k_ASYNC_OP_INVALID
= 0;
1815 const int64_t k_ASYNC_OP_UNSET
= -1;
1816 const int64_t k_ASYNC_OP_CONNECT
= -2;
1817 const int64_t k_ASYNC_OP_QUERY
= -3;