Don't return Variant& from Array functions
[hiphop-php.git] / hphp / runtime / ext / mysql / mysql_common.cpp
blob1b5188df01d83b92f9410adc3c20769b84855b86
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/mysql/mysql_common.h"
20 #include <algorithm>
21 #include <cassert>
22 #include <unordered_set>
23 #include <vector>
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"
55 namespace HPHP {
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,
72 int ms) {
73 #ifdef __APPLE__
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
77 return 0;
78 #endif
80 mysql_option opt = MYSQL_OPT_CONNECT_TIMEOUT;
81 #ifdef MYSQL_MILLISECOND_TIMEOUT
82 switch (type) {
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;
88 #else
89 switch (type) {
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;
96 #endif
98 return mysql_options(mysql, opt, (const char*)&ms);
101 ///////////////////////////////////////////////////////////////////////////////
103 void MySQLRequestData::requestInit() {
104 defaultConn.reset();
105 readTimeout = mysqlExtension::ReadTimeout;
106 totalRowCount = 0;
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;
132 if (mySQL) {
133 ret = mySQL->get();
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) {
143 ret = nullptr;
144 } else if (rconn) {
145 *rconn = mySQL;
147 return ret;
150 bool MySQL::CloseConn(const Variant& link_identifier) {
151 auto mySQL = Get(link_identifier);
152 if (mySQL) {
153 if (!mySQL->isPersistent()) {
154 mySQL->close();
155 } else {
156 s_cur_num_persistent--;
159 return true;
162 int MySQL::GetDefaultPort() {
163 if (s_default_port <= 0) {
164 s_default_port = MYSQL_PORT;
165 char *env = getenv("MYSQL_TCP_PORT");
166 if (env && *env) {
167 s_default_port = atoi(env);
168 } else {
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,
187 int client_flags) {
188 char buf[1024];
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);
195 namespace {
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,
204 int client_flags) {
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,
213 int client_flags,
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) {
225 return 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 ///////////////////////////////////////////////////////////////////////////////
246 // class MySQL
248 namespace {
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();
257 if (readTimeout) {
258 MySQLUtil::set_mysql_timeout(conn, MySQLUtil::ReadTimeout, readTimeout);
259 MySQLUtil::set_mysql_timeout(conn, MySQLUtil::WriteTimeout, readTimeout);
261 return conn;
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)
273 : m_port(port)
274 , m_last_error_set(false)
275 , m_last_errno(0)
276 , m_xaction_count(0)
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);
287 } else {
288 m_conn = create_new_conn();
292 void MySQL::setLastError(const char *func) {
293 assert(m_conn);
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() {
302 if (!m_conn) {
303 return;
305 m_last_error_set = false;
306 m_last_errno = 0;
307 m_xaction_count = 0;
308 m_last_error.clear();
309 mysql_close(m_conn);
310 m_conn = nullptr;
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,
323 connect_timeout);
325 if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLStats) {
326 ServerStats::Log("sql.conn", 1);
328 IOStatusHelper io("mysql::connect", host.data(), port);
329 m_xaction_count = 0;
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(),
337 password.data(),
338 (database.empty() ? nullptr : database.data()),
339 port,
340 socket.empty() ? nullptr : socket.data(),
341 client_flags);
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;
351 return ret;
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) {
358 bool ret = false;
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,
363 connect_timeout);
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(),
370 password.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());
380 return true;
381 } else {
382 if (connect_timeout >= 0) {
383 MySQLUtil::set_mysql_timeout(m_conn, MySQLUtil::ConnectTimeout,
384 connect_timeout);
386 if (RuntimeOption::EnableStats && RuntimeOption::EnableSQLStats) {
387 ServerStats::Log("sql.reconn_old", 1);
389 IOStatusHelper io("mysql::connect", host.data(), port);
390 m_xaction_count = 0;
391 ret = mysql_real_connect(m_conn, host.data(), username.data(),
392 password.data(),
393 (database.empty() ? nullptr : database.data()),
394 port, socket.data(), client_flags);
397 m_state = (ret) ? MySQLState::CONNECTED : MySQLState::CLOSED;
398 return ret;
401 ///////////////////////////////////////////////////////////////////////////////
402 // MySQLResource
404 IMPLEMENT_RESOURCE_ALLOCATION(MySQLResource)
406 ///////////////////////////////////////////////////////////////////////////////
407 // helpers
409 namespace {
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");
416 return nullptr;
418 return res;
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:
435 return "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:
441 return "int";
442 case FIELD_TYPE_FLOAT:
443 case FIELD_TYPE_DOUBLE:
444 case FIELD_TYPE_DECIMAL:
445 //case FIELD_TYPE_NEWDECIMAL:
446 return "real";
447 case FIELD_TYPE_TIMESTAMP:
448 return "timestamp";
449 case FIELD_TYPE_YEAR:
450 return "year";
451 case FIELD_TYPE_DATE:
452 case FIELD_TYPE_NEWDATE:
453 return "date";
454 case FIELD_TYPE_TIME:
455 return "time";
456 case FIELD_TYPE_SET:
457 return "set";
458 case FIELD_TYPE_ENUM:
459 return "enum";
460 case FIELD_TYPE_GEOMETRY:
461 return "geometry";
462 case FIELD_TYPE_DATETIME:
463 return "datetime";
464 case FIELD_TYPE_TINY_BLOB:
465 case FIELD_TYPE_MEDIUM_BLOB:
466 case FIELD_TYPE_LONG_BLOB:
467 case FIELD_TYPE_BLOB:
468 return "blob";
469 case FIELD_TYPE_NULL:
470 return "null";
471 default:
472 break;
474 return "unknown";
477 Variant php_mysql_field_info(const Resource& result, int field,
478 int entry_type) {
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:
489 return info->name;
490 case PHP_MYSQL_FIELD_TABLE:
491 return info->table;
492 case PHP_MYSQL_FIELD_LEN:
493 return info->length;
494 case PHP_MYSQL_FIELD_TYPE:
495 return php_mysql_get_field_name(info->type);
496 case PHP_MYSQL_FIELD_FLAGS:
498 char buf[512];
499 buf[0] = '\0';
500 unsigned int flags = info->flags;
501 #ifdef IS_NOT_NULL
502 if (IS_NOT_NULL(flags)) {
503 strcat(buf, "not_null ");
505 #endif
506 #ifdef IS_PRI_KEY
507 if (IS_PRI_KEY(flags)) {
508 strcat(buf, "primary_key ");
510 #endif
511 #ifdef UNIQUE_KEY_FLAG
512 if (flags & UNIQUE_KEY_FLAG) {
513 strcat(buf, "unique_key ");
515 #endif
516 #ifdef MULTIPLE_KEY_FLAG
517 if (flags & MULTIPLE_KEY_FLAG) {
518 strcat(buf, "multiple_key ");
520 #endif
521 #ifdef IS_BLOB
522 if (IS_BLOB(flags)) {
523 strcat(buf, "blob ");
525 #endif
526 #ifdef UNSIGNED_FLAG
527 if (flags & UNSIGNED_FLAG) {
528 strcat(buf, "unsigned ");
530 #endif
531 #ifdef ZEROFILL_FLAG
532 if (flags & ZEROFILL_FLAG) {
533 strcat(buf, "zerofill ");
535 #endif
536 #ifdef BINARY_FLAG
537 if (flags & BINARY_FLAG) {
538 strcat(buf, "binary ");
540 #endif
541 #ifdef ENUM_FLAG
542 if (flags & ENUM_FLAG) {
543 strcat(buf, "enum ");
545 #endif
546 #ifdef SET_FLAG
547 if (flags & SET_FLAG) {
548 strcat(buf, "set ");
550 #endif
551 #ifdef AUTO_INCREMENT_FLAG
552 if (flags & AUTO_INCREMENT_FLAG) {
553 strcat(buf, "auto_increment ");
555 #endif
556 #ifdef TIMESTAMP_FLAG
557 if (flags & TIMESTAMP_FLAG) {
558 strcat(buf, "timestamp ");
560 #endif
561 int len = strlen(buf);
562 /* remove trailing space, if present */
563 if (len && buf[len-1] == ' ') {
564 buf[len-1] = 0;
565 len--;
568 return String(buf, len, CopyString);
570 default:
571 break;
573 return false;
576 Variant php_mysql_do_connect(
577 const String& server,
578 const String& username,
579 const String& password,
580 const String& database,
581 int client_flags,
582 bool persistent,
583 bool async,
584 int connect_timeout_ms,
585 int query_timeout_ms,
586 const Array* conn_attrs) {
587 return php_mysql_do_connect_on_link(
588 nullptr,
589 server,
590 username,
591 password,
592 database,
593 client_flags,
594 persistent,
595 async,
596 connect_timeout_ms,
597 query_timeout_ms,
598 conn_attrs);
601 Variant php_mysql_do_connect_with_ssl(
602 const String& server,
603 const String& username,
604 const String& password,
605 const String& database,
606 int client_flags,
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()) {
613 auto* obj =
614 Native::data<HPHP::MySSLContextProvider>(sslContextProvider.toObject());
615 ssl_provider = obj->getSSLProvider();
618 return php_mysql_do_connect_on_link(
619 nullptr,
620 server,
621 username,
622 password,
623 database,
624 client_flags,
625 false,
626 false,
627 connect_timeout_ms,
628 query_timeout_ms,
629 conn_attrs,
630 ssl_provider);
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) {
637 return;
639 ssl_provider->setMysqlSSLOptions(mySQL->get());
642 static void mysql_set_conn_attr(MYSQL* mysql, const String& key,
643 const String& value) {
644 if (key.empty()) {
645 raise_warning("MySQL: Invalid connection attribute - empty key");
647 else if (value.empty()) {
648 raise_warning(
649 std::string("MySQL: Invalid connection attribute - empty value for ") +
650 key.toCppString());
652 else {
653 mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, key.c_str(),
654 value.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()) {
667 raise_warning(
668 "MySQL: Invalid connection attribute - key is not a string");
670 else if (!value.isString()) {
671 raise_warning(
672 std::string("MySQL: Invalid connection attribute - "
673 "value is not a string for key '") +
674 key.asCStrRef().toCppString() + "'");
676 else {
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) {
687 return;
689 ssl_provider->storeMysqlSSLSession(mySQL->get());
692 Variant php_mysql_do_connect_on_link(
693 std::shared_ptr<MySQL> mySQL,
694 String server,
695 String username,
696 String password,
697 String database,
698 int client_flags,
699 bool persistent,
700 bool async,
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
718 String host, socket;
719 int 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();
732 } else {
733 host = server;
734 port = MySQL::GetDefaultPort();
737 if (socket.empty()) {
738 socket = MySQL::GetDefaultSocket();
741 if (MySQL::IsAllowPersistent() &&
742 MySQL::GetCurrentNumPersistent() < MySQL::GetMaxNumPersistent() &&
743 persistent) {
744 auto p_mySQL = MySQL::GetPersistent(host, port, socket, username,
745 password, client_flags);
747 if (p_mySQL != nullptr) {
748 mySQL = p_mySQL;
749 } else {
750 savePersistent = true;
754 if (mySQL == nullptr) {
755 mySQL = std::make_shared<MySQL>(
756 host.c_str(),
757 port,
758 username.c_str(),
759 password.c_str(),
760 database.c_str());
763 // Set any connection attributes
764 if (conn_attrs != nullptr && conn_attrs->size() > 0) {
765 mysql_set_conn_attrs(mySQL, conn_attrs);
768 // set SSL Options
769 mysql_set_ssl_options(mySQL, ssl_provider);
771 if (mySQL->getState() == MySQLState::INITED) {
772 if (async) {
773 #ifdef FACEBOOK
774 if (!mySQL->async_connect(host, port, socket, username, password,
775 database)) {
776 MySQL::SetDefaultConn(mySQL); // so we can report errno by mysql_errno()
777 mySQL->setLastError("mysql_real_connect_nonblocking_init");
778 return false;
780 #else
781 throw_not_implemented("mysql_async_connect_start");
782 #endif
783 } else {
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");
788 return false;
791 } else {
792 if (!MySQL::IsAllowReconnect()) {
793 raise_warning("MySQL: Reconnects are not allowed");
794 return false;
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");
800 return false;
804 // store SSL Session
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 ///////////////////////////////////////////////////////////////////////////////
817 // MySQLResult
819 MySQLResult::MySQLResult(MYSQL_RES *res, bool localized /* = false */)
820 : m_res(res)
821 , m_current_async_row(nullptr)
822 , m_localized(localized)
823 , m_current_field(-1)
824 , m_conn(nullptr)
826 if (localized) {
827 m_res = nullptr; // ensure that localized results don't have another result
828 m_rows.emplace(1); // sentinel
829 m_current_row = m_rows->begin();
830 m_row_ready = false;
831 m_row_count = 0;
835 MySQLResult::~MySQLResult() {
836 close();
837 if (m_conn) {
838 m_conn = nullptr;
842 void MySQLResult::sweep() {
843 if (m_res) {
844 mysql_free_result(m_res);
845 m_res = nullptr;
849 void MySQLResult::addRow() {
850 m_row_count++;
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()) {
882 return NULL;
885 if (!m_localized && m_fields.empty()) {
886 if (m_res->fields == NULL) return NULL;
887 // cache field info
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()) {
898 return init_null();
900 return (*m_current_row)[field];
903 int64_t MySQLResult::getFieldCount() const {
904 if (!m_localized) {
905 return (int64_t)mysql_num_fields(m_res);
907 return m_fields.size();
910 int64_t MySQLResult::getRowCount() const {
911 if (!m_localized) {
912 return (int64_t)mysql_num_rows(m_res);
914 return m_row_count;
917 bool MySQLResult::seekRow(int64_t row) {
918 if (row < 0 || row >= getRowCount()) {
919 if (row != 0) {
920 raise_warning("Unable to jump to row %"
921 PRId64 " on MySQL result index %d",
922 row, getId());
924 return false;
927 if (!m_localized) {
928 mysql_data_seek(m_res, (my_ulonglong)row);
929 } else {
930 m_current_row = m_rows->begin();
931 for (int i = 0; i < row; i++) m_current_row++;
932 m_row_ready = false;
934 return true;
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()) {
942 m_row_ready = true;
943 return true;
945 return false;
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",
951 field, getId());
952 return false;
955 if (!m_localized) {
956 mysql_field_seek(m_res, (MYSQL_FIELD_OFFSET)field);
958 m_current_field = field - 1;
959 return true;
962 int64_t MySQLResult::tellField() {
963 if (!m_localized) {
964 return mysql_field_tell(m_res);
966 return m_current_field;
969 MySQLFieldInfo *MySQLResult::fetchFieldInfo() {
970 if (!m_localized) {
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++) {
988 m_null[i] = false;
989 m_length[i] = 0;
991 MYSQL_BIND *b = &m_vars[i];
992 b->is_null = &m_null[i];
993 b->length = &m_length[i];
994 b->buffer = nullptr;
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);
1008 req::free(m_vars);
1009 req::free(m_null);
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);
1029 break;
1030 case MYSQL_TYPE_LONGLONG:
1031 #if MYSQL_VERSION_ID > 50002
1032 case MYSQL_TYPE_BIT:
1033 #endif
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);
1041 break;
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 :
1062 fields[i].length;
1063 break;
1064 default:
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
1067 // server
1068 assert(false);
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];
1083 Variant v;
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;
1089 break;
1090 case MYSQL_TYPE_LONGLONG:
1091 v = *(int64_t*)b->buffer;
1092 break;
1093 case MYSQL_TYPE_STRING:
1094 v = String((char *)b->buffer, *b->length, CopyString);
1095 break;
1096 default:
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
1099 assert(false);
1103 tvSet(*v.asTypedValue(), m_arr.lvalAt(i));
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];
1112 switch (types[i]) {
1113 case 'i':
1114 b->buffer_type = MYSQL_TYPE_LONGLONG;
1115 break;
1116 case 'd':
1117 b->buffer_type = MYSQL_TYPE_DOUBLE;
1118 break;
1119 case 's':
1120 b->buffer_type = MYSQL_TYPE_STRING;
1121 break;
1122 case 'b':
1123 b->buffer_type = MYSQL_TYPE_LONG_BLOB;
1124 break;
1125 default:
1126 assert(false);
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).unboxed();
1138 Variant v;
1139 if (isNullType(var.type())) {
1140 *b->is_null = 1;
1141 } else {
1142 switch (b->buffer_type) {
1143 case MYSQL_TYPE_LONGLONG:
1145 m_value_arr.push_back(cellToInt(var.tv()));
1146 b->buffer = m_value_arr.back().getInt64Data();
1148 break;
1149 case MYSQL_TYPE_DOUBLE:
1151 m_value_arr.push_back(tvCastToDouble(var.tv()));
1152 b->buffer = m_value_arr.back().getDoubleData();
1154 break;
1155 case MYSQL_TYPE_STRING:
1157 m_value_arr.push_back(tvCastToString(var.tv()));
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();
1164 break;
1165 case MYSQL_TYPE_LONG_BLOB:
1166 // The value are set using send_long_data so we don't have to do
1167 // anything here
1168 break;
1169 default:
1170 assert(false);
1175 return !mysql_stmt_bind_param(stmt, m_vars);
1178 ///////////////////////////////////////////////////////////////////////////////
1179 // MySQLStmt
1181 #define VALIDATE_STMT \
1182 if (!m_stmt) { \
1183 raise_warning("Couldn't fetch mysqli_stmt"); \
1184 return init_null(); \
1187 #define VALIDATE_PREPARED \
1188 VALIDATE_STMT \
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() {
1199 close();
1202 void MySQLStmt::sweep() {
1203 close();
1204 // Note that ~MySQLStmt is *not* going to run when we are swept.
1207 Variant MySQLStmt::affected_rows() {
1208 VALIDATE_PREPARED
1209 return (int64_t)mysql_stmt_affected_rows(m_stmt);
1212 Variant MySQLStmt::attr_get(int64_t attr) {
1213 VALIDATE_PREPARED
1215 int64_t value = 0;
1217 if (mysql_stmt_attr_get(m_stmt, (enum_stmt_attr_type)attr, &value)) {
1218 return false;
1221 #if MYSQL_VERSION_ID >= 50107
1222 if ((enum_stmt_attr_type)attr == STMT_ATTR_UPDATE_MAX_LENGTH) {
1223 value = *(my_bool *)&value;
1225 #endif
1227 return value;
1230 Variant MySQLStmt::attr_set(int64_t attr, int64_t value) {
1231 VALIDATE_PREPARED
1233 #if MYSQL_VERSION_ID >= 50107
1234 if ((enum_stmt_attr_type)attr == STMT_ATTR_UPDATE_MAX_LENGTH) {
1235 value = (my_bool)value;
1237 #endif
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) {
1242 VALIDATE_PREPARED
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) {
1249 VALIDATE_PREPARED
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) {
1256 VALIDATE_PREPARED
1258 mysql_stmt_data_seek(m_stmt, offset);
1259 return init_null();
1262 Variant MySQLStmt::get_errno() {
1263 VALIDATE_STMT
1264 return (int64_t)mysql_stmt_errno(m_stmt);
1267 Variant MySQLStmt::get_error() {
1268 VALIDATE_STMT
1269 return String(mysql_stmt_error(m_stmt), CopyString);
1272 Variant MySQLStmt::close() {
1273 m_prepared = false;
1274 if (m_stmt) {
1275 bool ret = !mysql_stmt_close(m_stmt);
1276 m_stmt = nullptr;
1277 return ret;
1280 return true;
1283 Variant MySQLStmt::execute() {
1284 VALIDATE_PREPARED
1286 if (m_param_vars) {
1287 m_param_vars->bind_params(m_stmt);
1290 return !mysql_stmt_execute(m_stmt);
1293 Variant MySQLStmt::fetch() {
1294 VALIDATE_PREPARED
1296 int64_t ret = mysql_stmt_fetch(m_stmt);
1298 if (ret == MYSQL_DATA_TRUNCATED || ret == MYSQL_NO_DATA) {
1299 return init_null();
1302 if (ret) {
1303 return false;
1306 if (m_result_vars) {
1307 m_result_vars->update_result();
1310 return true;
1313 Variant MySQLStmt::field_count() {
1314 VALIDATE_PREPARED
1315 return (int64_t)mysql_stmt_field_count(m_stmt);
1318 Variant MySQLStmt::free_result() {
1319 VALIDATE_PREPARED
1320 return mysql_stmt_free_result(m_stmt);
1323 Variant MySQLStmt::insert_id() {
1324 VALIDATE_PREPARED
1325 return (int64_t)mysql_stmt_insert_id(m_stmt);
1328 Variant MySQLStmt::num_rows() {
1329 VALIDATE_PREPARED
1330 return (int64_t)mysql_stmt_num_rows(m_stmt);
1333 Variant MySQLStmt::param_count() {
1334 VALIDATE_PREPARED
1335 return (int64_t)mysql_stmt_param_count(m_stmt);
1338 Variant MySQLStmt::prepare(const String& query) {
1339 VALIDATE_STMT
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());
1346 return m_prepared;
1349 Variant MySQLStmt::reset() {
1350 VALIDATE_PREPARED
1351 return !mysql_stmt_reset(m_stmt);
1354 Variant MySQLStmt::result_metadata() {
1355 VALIDATE_PREPARED
1357 MYSQL_RES *mysql_result = mysql_stmt_result_metadata(m_stmt);
1358 if (!mysql_result) {
1359 return false;
1362 Array args;
1363 args.append(Variant(req::make<MySQLResult>(mysql_result)));
1365 auto cls = Unit::lookupClass(s_mysqli_result.get());
1366 Object obj{cls};
1368 tvDecRefGen(
1369 g_context->invokeFunc(cls->getCtor(), args, obj.get())
1371 return obj;
1374 Variant MySQLStmt::send_long_data(int64_t param_idx, const String& data) {
1375 VALIDATE_PREPARED
1376 return !mysql_stmt_send_long_data(m_stmt, param_idx, data.c_str(),
1377 data.size());
1380 Variant MySQLStmt::sqlstate() {
1381 VALIDATE_STMT
1382 return String(mysql_stmt_sqlstate(m_stmt), CopyString);
1385 Variant MySQLStmt::store_result() {
1386 VALIDATE_PREPARED
1387 return !mysql_stmt_store_result(m_stmt);
1390 #undef VALIDATE_STMT
1391 #undef VALIDATE_PREPARED
1393 ///////////////////////////////////////////////////////////////////////////////
1394 // query functions
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) {
1405 return init_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();
1420 default:
1421 break;
1424 return data;
1427 #ifdef FACEBOOK
1428 extern "C" {
1429 struct MEM_ROOT;
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;
1437 unsigned char *cp;
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) {
1443 return false;
1446 res->setFieldCount((int64_t)fields);
1448 // localizes all the rows
1449 while (*(cp = net->read_pos) != 254 || pkt_len >= 8) {
1450 res->addRow();
1451 for (unsigned int i = 0; i < fields; i++) {
1452 unsigned long len = net_field_length(&cp);
1453 Variant data;
1454 if (len != NULL_LENGTH) {
1455 data = mysql_makevalue(String((char *)cp, len, CopyString),
1456 mysql->fields + i);
1457 cp += len;
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) {
1466 return false;
1470 // localizes all the field info
1471 for (unsigned int i = 0; i < fields; i++) {
1472 res->setFieldInfo((int64_t)i, mysql->fields + i);
1475 return true;
1478 static Variant php_mysql_localize_result(MYSQL *mysql) {
1479 #if MYSQL_VERSION_ID <= 50138
1480 mysql = mysql->last_used_con;
1481 #endif
1483 if (!mysql->fields) return true;
1484 if (mysql->status != MYSQL_STATUS_GET_RESULT) {
1485 // consistent with php_mysql_do_query_and_get_result
1486 return true;
1488 mysql->status = MYSQL_STATUS_READY;
1489 Variant result(req::make<MySQLResult>(nullptr, true));
1490 if (!php_mysql_read_rows(mysql, result)) {
1491 return false;
1494 // clean up
1495 if (mysql->fields) {
1496 free_root(&mysql->field_alloc, 0);
1498 mysql->unbuffered_fetch_owner = 0;
1500 return result;
1502 #endif // FACEBOOK
1504 MySQLQueryReturn php_mysql_do_query(const String& query, const Variant& link_id,
1505 bool async_mode) {
1506 SYNC_VM_REGS_SCOPED();
1507 if (mysqlExtension::ReadOnly &&
1508 same(preg_match("/^((\\/\\*.*?\\*\\/)|\\(|\\s)*select/i", query),
1509 0)) {
1510 raise_notice("runtime/ext_mysql: write query not executed [%s]",
1511 query.data());
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.
1524 Variant result;
1525 String q = preg_replace(result, "/\\/\\*.*?\\*\\//", " ", query) ?
1526 result.toString() : query;
1528 Variant matches;
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",
1536 q, &matches);
1537 auto marray = matches.toArray();
1538 int size = marray.size();
1539 if (size > 2) {
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,]+)[\\+\\-]/",
1550 q, &matches);
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;
1562 } else {
1563 preg_match("/^(?:(?:\\/\\*.*?\\*\\/)|\\(|\\s)*"
1564 "(start transaction|begin|commit|rollback|select)/is",
1565 query, &matches);
1566 auto marray = matches.toArray();
1567 size = marray.size();
1568 if (size == 2) {
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);
1576 } else {
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);
1590 if (!mySQL) {
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;
1599 if (async_mode) {
1600 #ifdef FACEBOOK
1601 mySQL->m_async_query = query.toCppString();
1602 return MySQLQueryReturn::OK;
1603 #else
1604 throw_not_implemented("mysql_async_query_start");
1605 #endif
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));
1614 #endif
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 */) {
1624 Variant ret =
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(),
1629 rconn->m_port);
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);
1633 if (connected) {
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;
1650 } else {
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;
1661 if (use_store) {
1662 #ifdef FACEBOOK
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);
1672 #endif
1673 mysql_result = mysql_store_result(conn);
1674 } else {
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");
1680 return false;
1682 return true;
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);
1704 switch (result) {
1705 case MySQLQueryReturn::OK_FETCH_RESULT:
1706 return php_mysql_get_result(link_id, use_store);
1707 case MySQLQueryReturn::OK:
1708 return true;
1709 case MySQLQueryReturn::FAIL:
1710 return false;
1713 not_reached();
1716 ///////////////////////////////////////////////////////////////////////////////
1717 // row operations
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);
1722 return false;
1725 auto res = php_mysql_extract_result(result);
1726 if (!res) return false;
1728 Array ret;
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));
1741 return ret;
1744 MYSQL_RES *mysql_result = res->get();
1745 MYSQL_ROW mysql_row = mysql_fetch_row(mysql_result);
1746 if (!mysql_row) {
1747 return false;
1749 unsigned long *mysql_row_lengths = mysql_fetch_lengths(mysql_result);
1750 if (!mysql_row_lengths) {
1751 return false;
1754 mysql_field_seek(mysql_result, 0);
1756 MYSQL_FIELD *mysql_field;
1757 int i;
1758 for (mysql_field = mysql_fetch_field(mysql_result), i = 0; mysql_field;
1759 mysql_field = mysql_fetch_field(mysql_result), i++) {
1760 Variant data;
1761 if (mysql_row[i]) {
1762 data = mysql_makevalue(String(mysql_row[i], mysql_row_lengths[i],
1763 CopyString), mysql_field);
1765 if (result_type & PHP_MYSQL_NUM) {
1766 ret.set(i, data);
1768 if (result_type & PHP_MYSQL_ASSOC) {
1769 ret.set(String(mysql_field->name, CopyString), data);
1772 return ret;
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. */
1778 #ifdef FACEBOOK
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;
1808 return ret;
1811 #else // FACEBOOK
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;
1819 #endif