1 /*
2  +----------------------------------------------------------------------+
3  | Swoole                                                               |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 2012-2018 The Swoole Group                             |
6  +----------------------------------------------------------------------+
7  | This source file is subject to version 2.0 of the Apache license,    |
8  | that is bundled with this package in the file LICENSE, and is        |
9  | available through the world-wide-web at the following url:           |
10  | http://www.apache.org/licenses/LICENSE-2.0.html                      |
11  | If you did not receive a copy of the Apache2.0 license and are unable|
12  | to obtain it through the world-wide-web, please send a note to       |
13  | license@swoole.com so we can mail you a copy immediately.            |
14  +----------------------------------------------------------------------+
15  | Author: Twosee  <twose@qq.com>                                       |
16  | Author: Tianfeng Han  <mikan.tenny@gmail.com>                        |
17  +----------------------------------------------------------------------+
18  */
19 
20 #include "php_swoole_cxx.h"
21 #include "php_swoole_mysql_proto.h"
22 
23 #include "swoole_string.h"
24 
25 // see mysqlnd 'L64' macro redefined
26 #undef L64
27 
28 SW_EXTERN_C_BEGIN
29 #include "ext/hash/php_hash.h"
30 #include "ext/hash/php_hash_sha.h"
31 #include "ext/standard/php_math.h"
32 #ifdef SW_USE_MYSQLND
33 #include "ext/mysqlnd/mysqlnd.h"
34 #include "ext/mysqlnd/mysqlnd_charset.h"
35 #endif
36 SW_EXTERN_C_END
37 
38 #include <unordered_map>
39 
40 /* keep same with pdo and mysqli */
41 #define MYSQLND_UNKNOWN_SQLSTATE "HY000"
42 #define MYSQLND_SERVER_GONE "MySQL server has gone away"
43 #define MYSQLND_CR_UNKNOWN_ERROR 2000
44 #define MYSQLND_CR_CONNECTION_ERROR 2002
45 #define MYSQLND_CR_SERVER_GONE_ERROR 2006
46 #define MYSQLND_CR_OUT_OF_MEMORY 2008
47 #define MYSQLND_CR_SERVER_LOST 2013
48 #define MYSQLND_CR_COMMANDS_OUT_OF_SYNC 2014
49 #define MYSQLND_CR_CANT_FIND_CHARSET 2019
50 #define MYSQLND_CR_MALFORMED_PACKET 2027
51 #define MYSQLND_CR_NOT_IMPLEMENTED 2054
52 #define MYSQLND_CR_NO_PREPARE_STMT 2030
53 #define MYSQLND_CR_PARAMS_NOT_BOUND 2031
54 #define MYSQLND_CR_INVALID_PARAMETER_NO 2034
55 #define MYSQLND_CR_INVALID_BUFFER_USE 2035
56 
57 using swoole::coroutine::Socket;
58 using namespace swoole;
59 
60 namespace swoole {
61 class mysql_statement;
62 class mysql_client {
63   public:
64     /* session related {{{ */
65     Socket *socket = nullptr;
66     Socket::timeout_controller *tc = nullptr;
67 
68     enum sw_mysql_state state = SW_MYSQL_STATE_CLOSED;
69     bool quit = false;
70     mysql::result_info result;
71 
72     std::unordered_map<uint32_t, mysql_statement *> statements;
73     mysql_statement *statement = nullptr;
74     /* }}} */
75 
76     std::string host = SW_MYSQL_DEFAULT_HOST;
77     uint16_t port = SW_MYSQL_DEFAULT_PORT;
78     bool ssl = false;
79 
80     std::string user = "root";
81     std::string password = "root";
82     std::string database = "test";
83     char charset = SW_MYSQL_DEFAULT_CHARSET;
84 
85     double connect_timeout = network::Socket::default_connect_timeout;
86     bool strict_type = false;
87 
get_error_code()88     inline int get_error_code() {
89         return error_code;
90     }
91 
get_error_msg()92     inline const char *get_error_msg() {
93         return error_msg.c_str();
94     }
95 
non_sql_error(int code,const char * msg)96     inline void non_sql_error(int code, const char *msg) {
97         error_code = code;
98         error_msg = std_string::format("SQLSTATE[" MYSQLND_UNKNOWN_SQLSTATE "] [%d] %s", code, msg);
99     }
100 
101     template <typename... Args>
non_sql_error(int code,const char * format,Args...args)102     inline void non_sql_error(int code, const char *format, Args... args) {
103         error_code = code;
104         error_msg = std_string::format(
105             "SQLSTATE[" MYSQLND_UNKNOWN_SQLSTATE "] [%d] %s", code, std_string::format(format, args...).c_str());
106     }
107 
io_error()108     void io_error() {
109         if (state == SW_MYSQL_STATE_CLOSED) {
110             non_sql_error(MYSQLND_CR_CONNECTION_ERROR, socket->errMsg);
111         } else {
112             non_sql_error(MYSQLND_CR_SERVER_GONE_ERROR,
113                           MYSQLND_SERVER_GONE "%s%s",
114                           socket->errCode ? " due to " : "",
115                           socket->errCode ? socket->errMsg : "");
116         }
117         /* don't send QUIT after IO error */
118         quit = true;
119         close();
120     }
121 
proto_error(const char * data,const enum sw_mysql_packet_types expected_type)122     void proto_error(const char *data, const enum sw_mysql_packet_types expected_type) {
123         mysql::server_packet packet(data);
124         non_sql_error(MYSQLND_CR_MALFORMED_PACKET,
125                       "Unexpected mysql packet length=%u, number=%u, type=%u, expected_type=%u",
126                       packet.header.length,
127                       packet.header.number,
128                       (uint8_t) data[SW_MYSQL_PACKET_HEADER_SIZE],
129                       expected_type);
130         close();
131     }
132 
server_error(const char * data)133     void server_error(const char *data) {
134         mysql::err_packet err_packet(data);
135         error_code = err_packet.code;
136         error_msg =
137             std_string::format("SQLSTATE[%s] [%d] %s", err_packet.sql_state, err_packet.code, err_packet.msg.c_str());
138         state = SW_MYSQL_STATE_IDLE;
139     }
140 
get_fetch_mode()141     inline bool get_fetch_mode() {
142         return fetch_mode;
143     }
144 
set_fetch_mode(bool v)145     inline bool set_fetch_mode(bool v) {
146         if (sw_unlikely(socket && v)) {
147             non_sql_error(ENOTSUP, "Can not use fetch mode after the connection is established");
148             return false;
149         }
150         fetch_mode = v;
151         return true;
152     }
153 
get_defer()154     inline bool get_defer() {
155         return defer;
156     }
157 
set_defer(bool v)158     inline bool set_defer(bool v) {
159         // if (sw_unlikely(fetch_mode && v))
160         // {
161         //      non_sql_error(ENOTSUP, "Can not use defer mode when fetch mode is on");
162         //    return false;
163         // }
164         defer = v;
165         return true;
166     }
167 
add_timeout_controller(double timeout,const enum Socket::TimeoutType type)168     void add_timeout_controller(double timeout, const enum Socket::TimeoutType type) {
169         if (sw_unlikely(!socket)) {
170             return;
171         }
172         // Notice: `timeout > 0` is wrong, maybe -1
173         if (timeout != 0) {
174             SW_ASSERT(!tc);
175             tc = new Socket::timeout_controller(socket, timeout, type);
176         }
177     }
178 
has_timedout(enum Socket::TimeoutType type)179     inline bool has_timedout(enum Socket::TimeoutType type) {
180         return tc && tc->has_timedout(type);
181     }
182 
del_timeout_controller()183     void del_timeout_controller() {
184         if (tc) {
185             delete tc;
186             tc = nullptr;
187         }
188     }
189 
190     bool connect(std::string host, uint16_t port, bool ssl);
191 
connect()192     inline bool connect() {
193         return connect(host, port, ssl);
194     }
195 
is_connected()196     inline bool is_connected() {
197         return socket && socket->is_connected();
198     }
199 
get_fd()200     inline int get_fd() {
201         return socket ? socket->get_fd() : -1;
202     }
203 
check_connection()204     inline bool check_connection() {
205         if (sw_unlikely(!is_connected())) {
206             non_sql_error(MYSQLND_CR_CONNECTION_ERROR, "%s or %s", strerror(ECONNRESET), strerror(ENOTCONN));
207             return false;
208         }
209         return true;
210     }
211 
check_liveness()212     inline bool check_liveness() {
213         if (sw_unlikely(!check_connection())) {
214             return false;
215         }
216         if (sw_unlikely(!socket->check_liveness())) {
217             non_sql_error(MYSQLND_CR_SERVER_GONE_ERROR, MYSQLND_SERVER_GONE);
218             close();
219             return false;
220         }
221         return true;
222     }
223 
is_writable()224     inline bool is_writable() {
225         return is_connected() && !socket->has_bound(SW_EVENT_WRITE);
226     }
227 
is_available_for_new_request()228     bool is_available_for_new_request() {
229         if (sw_unlikely(state != SW_MYSQL_STATE_IDLE && state != SW_MYSQL_STATE_CLOSED)) {
230             if (socket) {
231                 socket->check_bound_co(SW_EVENT_RDWR);
232             }
233             non_sql_error(EINPROGRESS,
234                           "MySQL client is busy now on state#%d, "
235                           "please use recv/fetchAll/nextResult to get all unread data "
236                           "and wait for response then try again",
237                           state);
238             return false;
239         }
240         if (sw_unlikely(!check_liveness())) {
241             return false;
242         } else {
243             /* without unread data */
244             String *buffer = socket->get_read_buffer();
245             SW_ASSERT(buffer->length == (size_t) buffer->offset);
246             buffer->clear();
247             return true;
248         }
249     }
250 
251     const char *recv_packet();
252 
recv_none_error_packet()253     inline const char *recv_none_error_packet() {
254         const char *data = recv_packet();
255         if (sw_unlikely(data && mysql::server_packet::is_err(data))) {
256             server_error(data);
257             return nullptr;
258         }
259         return data;
260     }
261 
recv_eof_packet()262     inline const char *recv_eof_packet() {
263         const char *data = recv_packet();
264         if (sw_unlikely(data && !mysql::server_packet::is_eof(data))) {
265             proto_error(data, SW_MYSQL_PACKET_EOF);
266             return nullptr;
267         }
268 #ifdef SW_LOG_TRACE_OPEN
269         mysql::eof_packet eof_packet(data);
270 #endif
271         return data;
272     }
273 
send_raw(const char * data,size_t length)274     inline bool send_raw(const char *data, size_t length) {
275         if (sw_unlikely(!check_connection())) {
276             return false;
277         } else {
278             if (sw_unlikely(has_timedout(Socket::TIMEOUT_WRITE))) {
279                 io_error();
280                 return false;
281             }
282             if (sw_unlikely(socket->send_all(data, length) != (ssize_t) length)) {
283                 io_error();
284                 return false;
285             }
286             return true;
287         }
288     }
289 
290     bool send_packet(mysql::client_packet *packet);
291     bool send_command(enum sw_mysql_command command, const char *sql = nullptr, size_t length = 0);
292     // just for internal
293     void send_command_without_check(enum sw_mysql_command command, const char *sql = nullptr, size_t length = 0);
294 
295     void query(zval *return_value, const char *statement, size_t statement_length);
296     void send_query_request(zval *return_value, const char *statement, size_t statement_length);
297     void recv_query_response(zval *return_value);
298     const char *handle_row_data_size(mysql::row_data *row_data, uint8_t size);
299     bool handle_row_data_lcb(mysql::row_data *row_data);
300     void handle_row_data_text(zval *return_value, mysql::row_data *row_data, mysql::field_packet *field);
301     void handle_strict_type(zval *ztext, mysql::field_packet *field);
302     void fetch(zval *return_value);
303     void fetch_all(zval *return_value);
304     void next_result(zval *return_value);
305     bool recv();
306 
307     bool send_prepare_request(const char *statement, size_t statement_length);
308     mysql_statement *recv_prepare_response();
309 
310     void close();
311 
~mysql_client()312     ~mysql_client() {
313         SW_ASSERT(statements.empty());
314         close();
315     }
316 
317   private:
318     int error_code = 0;
319     std::string error_msg = "";
320 
321     /* unable to support both features at the same time, so we have to set them by method {{{ */
322     bool fetch_mode = false;
323     bool defer = false;
324     /* }}} */
325 
326     // recv data of specified length
327     const char *recv_length(size_t need_length, const bool try_to_recycle = false);
328     // usually mysql->connect = connect(TCP) + handshake
329     bool handshake();
330 };
331 
332 class mysql_statement {
333   public:
334     std::string statement;
335     mysql::statement info;
336     mysql::result_info result;
337 
mysql_statement(mysql_client * client,const char * statement,size_t statement_length)338     mysql_statement(mysql_client *client, const char *statement, size_t statement_length) : client(client) {
339         this->statement = std::string(statement, statement_length);
340     }
341 
get_client()342     inline mysql_client *get_client() {
343         return client;
344     }
345 
get_error_code()346     inline int get_error_code() {
347         return sw_likely(client) ? client->get_error_code() : error_code;
348     }
349 
get_error_msg()350     inline const char *get_error_msg() {
351         return sw_likely(client) ? client->get_error_msg() : error_msg.c_str();
352     }
353 
is_available()354     inline bool is_available() {
355         if (sw_unlikely(!client)) {
356             error_code = ECONNRESET;
357             error_msg = "statement must to be recompiled after the connection is broken";
358             return false;
359         }
360         return true;
361     }
362 
is_available_for_new_request()363     inline bool is_available_for_new_request() {
364         if (sw_unlikely(!is_available())) {
365             return false;
366         }
367         if (sw_unlikely(!client->is_available_for_new_request())) {
368             return false;
369         }
370         return true;
371     }
372 
add_timeout_controller(double timeout,const enum Socket::TimeoutType type)373     inline void add_timeout_controller(double timeout, const enum Socket::TimeoutType type) {
374         if (sw_likely(client)) {
375             client->add_timeout_controller(timeout, type);
376         }
377     }
378 
del_timeout_controller()379     inline void del_timeout_controller() {
380         if (sw_likely(client)) {
381             client->del_timeout_controller();
382         }
383     }
384 
385     // [notify = false]: mysql_client actively close
close(const bool notify=true)386     inline void close(const bool notify = true) {
387         if (client) {
388             // if client point exists, socket is always available
389             if (notify) {
390                 if (sw_likely(client->is_writable())) {
391                     char id[4];
392                     sw_mysql_int4store(id, info.id);
393                     client->send_command_without_check(SW_MYSQL_COM_STMT_CLOSE, id, sizeof(id));
394                 }
395                 client->statements.erase(info.id);
396             } else {
397                 error_code = client->get_error_code();
398                 error_msg = client->get_error_msg();
399             }
400             client = nullptr;
401         }
402     }
403 
~mysql_statement()404     ~mysql_statement() {
405         close();
406     }
407 
408     bool send_prepare_request();
409     bool recv_prepare_response();
410 
411     void execute(zval *return_value, zval *params);
412     void send_execute_request(zval *return_value, zval *params);
413     void recv_execute_response(zval *return_value);
414 
415     void fetch(zval *return_value);
416     void fetch_all(zval *return_value);
417     void next_result(zval *return_value);
418 
419   private:
420     mysql_client *client = nullptr;
421     int error_code = 0;
422     std::string error_msg;
423 };
424 }  // namespace swoole
425 
426 using swoole::mysql_client;
427 using swoole::mysql_statement;
428 
429 static zend_class_entry *swoole_mysql_coro_ce;
430 static zend_object_handlers swoole_mysql_coro_handlers;
431 
432 static zend_class_entry *swoole_mysql_coro_exception_ce;
433 static zend_object_handlers swoole_mysql_coro_exception_handlers;
434 
435 static zend_class_entry *swoole_mysql_coro_statement_ce;
436 static zend_object_handlers swoole_mysql_coro_statement_handlers;
437 
438 struct mysql_coro_t {
439     mysql_client *client;
440     zend_object std;
441 };
442 
443 struct mysql_coro_statement_t {
444     mysql_statement *statement;
445     zend_object *zclient;
446     zend_object std;
447 };
448 
449 SW_EXTERN_C_BEGIN
450 static PHP_METHOD(swoole_mysql_coro, __construct);
451 static PHP_METHOD(swoole_mysql_coro, __destruct);
452 static PHP_METHOD(swoole_mysql_coro, connect);
453 static PHP_METHOD(swoole_mysql_coro, getDefer);
454 static PHP_METHOD(swoole_mysql_coro, setDefer);
455 static PHP_METHOD(swoole_mysql_coro, query);
456 static PHP_METHOD(swoole_mysql_coro, fetch);
457 static PHP_METHOD(swoole_mysql_coro, fetchAll);
458 static PHP_METHOD(swoole_mysql_coro, nextResult);
459 static PHP_METHOD(swoole_mysql_coro, prepare);
460 static PHP_METHOD(swoole_mysql_coro, recv);
461 static PHP_METHOD(swoole_mysql_coro, begin);
462 static PHP_METHOD(swoole_mysql_coro, commit);
463 static PHP_METHOD(swoole_mysql_coro, rollback);
464 #ifdef SW_USE_MYSQLND
465 static PHP_METHOD(swoole_mysql_coro, escape);
466 #endif
467 static PHP_METHOD(swoole_mysql_coro, close);
468 
469 static PHP_METHOD(swoole_mysql_coro_statement, execute);
470 static PHP_METHOD(swoole_mysql_coro_statement, fetch);
471 static PHP_METHOD(swoole_mysql_coro_statement, fetchAll);
472 static PHP_METHOD(swoole_mysql_coro_statement, nextResult);
473 static PHP_METHOD(swoole_mysql_coro_statement, recv);
474 static PHP_METHOD(swoole_mysql_coro_statement, close);
475 SW_EXTERN_C_END
476 
477 // clang-format off
478 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_void, 0, 0, 0)
479 ZEND_END_ARG_INFO()
480 
481 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_optional_timeout, 0, 0, 0)
482     ZEND_ARG_INFO(0, timeout)
483 ZEND_END_ARG_INFO()
484 
485 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_connect, 0, 0, 0)
486     ZEND_ARG_ARRAY_INFO(0, server_config, 0)
487 ZEND_END_ARG_INFO()
488 
489 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_query, 0, 0, 1)
490     ZEND_ARG_INFO(0, sql)
491     ZEND_ARG_INFO(0, timeout)
492 ZEND_END_ARG_INFO()
493 
494 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_prepare, 0, 0, 1)
495     ZEND_ARG_INFO(0, query)
496     ZEND_ARG_INFO(0, timeout)
497 ZEND_END_ARG_INFO()
498 
499 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_setDefer, 0, 0, 0)
500     ZEND_ARG_INFO(0, defer)
501 ZEND_END_ARG_INFO()
502 
503 #ifdef SW_USE_MYSQLND
504 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_escape, 0, 0, 1)
505     ZEND_ARG_INFO(0, string)
506     ZEND_ARG_INFO(0, flags)
507 ZEND_END_ARG_INFO()
508 #endif
509 
510 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_statement_execute, 0, 0, 0)
511     ZEND_ARG_INFO(0, params)
512     ZEND_ARG_INFO(0, timeout)
513 ZEND_END_ARG_INFO()
514 
515 static const zend_function_entry swoole_mysql_coro_methods[] =
516 {
517     PHP_ME(swoole_mysql_coro, __construct, arginfo_swoole_void, ZEND_ACC_PUBLIC)
518     PHP_ME(swoole_mysql_coro, __destruct, arginfo_swoole_void, ZEND_ACC_PUBLIC)
519     PHP_ME(swoole_mysql_coro, getDefer, arginfo_swoole_void, ZEND_ACC_PUBLIC)
520     PHP_ME(swoole_mysql_coro, setDefer, arginfo_swoole_mysql_coro_setDefer, ZEND_ACC_PUBLIC)
521     PHP_ME(swoole_mysql_coro, connect, arginfo_swoole_mysql_coro_connect, ZEND_ACC_PUBLIC)
522     PHP_ME(swoole_mysql_coro, query, arginfo_swoole_mysql_coro_query, ZEND_ACC_PUBLIC)
523     PHP_ME(swoole_mysql_coro, fetch, arginfo_swoole_void, ZEND_ACC_PUBLIC)
524     PHP_ME(swoole_mysql_coro, fetchAll, arginfo_swoole_void, ZEND_ACC_PUBLIC)
525     PHP_ME(swoole_mysql_coro, nextResult, arginfo_swoole_void, ZEND_ACC_PUBLIC)
526     PHP_ME(swoole_mysql_coro, prepare, arginfo_swoole_mysql_coro_prepare, ZEND_ACC_PUBLIC)
527     PHP_ME(swoole_mysql_coro, recv, arginfo_swoole_void, ZEND_ACC_PUBLIC)
528     PHP_ME(swoole_mysql_coro, begin, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC)
529     PHP_ME(swoole_mysql_coro, commit, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC)
530     PHP_ME(swoole_mysql_coro, rollback, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC)
531 #ifdef SW_USE_MYSQLND
532     PHP_ME(swoole_mysql_coro, escape, arginfo_swoole_mysql_coro_escape, ZEND_ACC_PUBLIC)
533 #endif
534     PHP_ME(swoole_mysql_coro, close, arginfo_swoole_void, ZEND_ACC_PUBLIC)
535     PHP_FE_END
536 };
537 
538 static const zend_function_entry swoole_mysql_coro_statement_methods[] =
539 {
540     PHP_ME(swoole_mysql_coro_statement, execute, arginfo_swoole_mysql_coro_statement_execute, ZEND_ACC_PUBLIC)
541     PHP_ME(swoole_mysql_coro_statement, fetch, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC)
542     PHP_ME(swoole_mysql_coro_statement, fetchAll, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC)
543     PHP_ME(swoole_mysql_coro_statement, nextResult, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC)
544     PHP_ME(swoole_mysql_coro_statement, recv, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC)
545     PHP_ME(swoole_mysql_coro_statement, close, arginfo_swoole_void, ZEND_ACC_PUBLIC)
546     PHP_FE_END
547 };
548 // clang-format on
549 
php_swoole_sha256(const char * str,int len,unsigned char * digest)550 void php_swoole_sha256(const char *str, int len, unsigned char *digest) {
551     PHP_SHA256_CTX context;
552     PHP_SHA256Init(&context);
553     PHP_SHA256Update(&context, (unsigned char *) str, len);
554     PHP_SHA256Final(digest, &context);
555 }
556 
connect(std::string host,uint16_t port,bool ssl)557 bool mysql_client::connect(std::string host, uint16_t port, bool ssl) {
558     if (socket && (host != this->host || port != this->port || ssl != this->ssl)) {
559         close();
560     }
561     if (!socket) {
562         if (host.compare(0, 6, "unix:/", 0, 6) == 0) {
563             host = host.substr(sizeof("unix:") - 1);
564             host.erase(0, host.find_first_not_of('/') - 1);
565             socket = new Socket(SW_SOCK_UNIX_STREAM);
566         } else if (host.find(':') != std::string::npos) {
567             socket = new Socket(SW_SOCK_TCP6);
568         } else {
569             socket = new Socket(SW_SOCK_TCP);
570         }
571         if (sw_unlikely(socket->get_fd() < 0)) {
572             php_swoole_fatal_error(E_WARNING, "new Socket() failed. Error: %s [%d]", strerror(errno), errno);
573             non_sql_error(MYSQLND_CR_CONNECTION_ERROR, strerror(errno));
574             delete socket;
575             socket = nullptr;
576             return false;
577         }
578         socket->set_zero_copy(true);
579 #ifdef SW_USE_OPENSSL
580         if (ssl) {
581             socket->enable_ssl_encrypt();
582         }
583 #endif
584         socket->set_timeout(connect_timeout, Socket::TIMEOUT_CONNECT);
585         add_timeout_controller(connect_timeout, Socket::TIMEOUT_ALL);
586         if (!socket->connect(host, port)) {
587             io_error();
588             return false;
589         }
590         this->host = host;
591         this->port = port;
592 #ifdef SW_USE_OPENSSL
593         this->ssl = ssl;
594 #endif
595         if (!handshake()) {
596             close();
597             return false;
598         }
599         state = SW_MYSQL_STATE_IDLE;
600         quit = false;
601         del_timeout_controller();
602     }
603     return true;
604 }
605 
recv_length(size_t need_length,const bool try_to_recycle)606 const char *mysql_client::recv_length(size_t need_length, const bool try_to_recycle) {
607     if (sw_likely(check_connection())) {
608         ssize_t retval;
609         String *buffer = socket->get_read_buffer();
610         off_t offset = buffer->offset;                    // save offset instead of buffer point (due to realloc)
611         size_t read_n = buffer->length - buffer->offset;  // readable bytes
612         if (try_to_recycle && read_n == 0) {
613             swoole_trace_log(SW_TRACE_MYSQL_CLIENT,
614                        "mysql buffer will be recycled, length=%zu, offset=%jd",
615                        buffer->length,
616                        (intmax_t) offset);
617             buffer->clear();
618             offset = 0;
619         }
620         while (read_n < need_length) {
621             if (sw_unlikely(has_timedout(Socket::TIMEOUT_READ))) {
622                 io_error();
623                 return nullptr;
624             }
625             if (sw_unlikely(buffer->length == buffer->size)) {
626                 /* offset + need_length = new size (min) */
627                 if (!buffer->extend(SW_MEM_ALIGNED_SIZE_EX(offset + need_length, SwooleG.pagesize))) {
628                     non_sql_error(MYSQLND_CR_OUT_OF_MEMORY, strerror(ENOMEM));
629                     return nullptr;
630                 } else {
631                     swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "mysql buffer extend to %zu", buffer->size);
632                 }
633             }
634             retval = socket->recv(buffer->str + buffer->length, buffer->size - buffer->length);
635             if (sw_unlikely(retval <= 0)) {
636                 io_error();
637                 return nullptr;
638             }
639             read_n += retval;
640             buffer->length += retval;
641         }
642         buffer->offset += need_length;
643         return buffer->str + offset;
644     }
645     return nullptr;
646 }
647 
recv_packet()648 const char *mysql_client::recv_packet() {
649     const char *p;
650     uint32_t length;
651     p = recv_length(SW_MYSQL_PACKET_HEADER_SIZE, true);
652     if (sw_unlikely(!p)) {
653         return nullptr;
654     }
655     length = mysql::packet::get_length(p);
656     swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "recv packet length=%u, number=%u", length, mysql::packet::get_number(p));
657     p = recv_length(length);
658     if (sw_unlikely(!p)) {
659         return nullptr;
660     }
661     /* Notice: why we do this? because buffer maybe reallocated when recv data */
662     return p - SW_MYSQL_PACKET_HEADER_SIZE;
663 }
664 
send_packet(mysql::client_packet * packet)665 bool mysql_client::send_packet(mysql::client_packet *packet) {
666     const char *data = packet->get_data();
667     uint32_t length = SW_MYSQL_PACKET_HEADER_SIZE + packet->get_length();
668     if (sw_likely(send_raw(data, length))) {
669         return true;
670     }
671     return false;
672 }
673 
send_command(enum sw_mysql_command command,const char * sql,size_t length)674 bool mysql_client::send_command(enum sw_mysql_command command, const char *sql, size_t length) {
675     if (sw_likely(SW_MYSQL_PACKET_HEADER_SIZE + 1 + length <= SwooleG.pagesize)) {
676         mysql::command_packet command_packet(command, sql, length);
677         return send_raw(command_packet.get_data(), command_packet.get_data_length());
678     } else {
679         /* if the data is larger than page_size, copy memory to the kernel buffer multiple times is much faster */
680         size_t send_s = SW_MIN(length, SW_MYSQL_MAX_PACKET_BODY_SIZE - 1), send_n = send_s, number = 0;
681         mysql::command_packet command_packet(command);
682         command_packet.set_header(1 + send_s, number++);
683 
684         if (sw_unlikely(!send_raw(command_packet.get_data(), SW_MYSQL_PACKET_HEADER_SIZE + 1)) ||
685             !send_raw(sql, send_s)) {
686             return false;
687         }
688         /* MySQL single packet size is 16M, we must subpackage */
689         while (send_n < length) {
690             send_s = length - send_n;
691             send_s = SW_MIN(send_s, SW_MYSQL_MAX_PACKET_BODY_SIZE);
692             command_packet.set_header(send_s, number++);
693             if (sw_unlikely(!send_raw(command_packet.get_data(), SW_MYSQL_PACKET_HEADER_SIZE)) ||
694                 !send_raw(sql + send_n, send_s)) {
695                 return false;
696             }
697             send_n += send_s;
698         }
699         return true;
700     }
701 }
702 
send_command_without_check(enum sw_mysql_command command,const char * sql,size_t length)703 void mysql_client::send_command_without_check(enum sw_mysql_command command, const char *sql, size_t length) {
704     mysql::command_packet command_packet(command, sql, length);
705     (void) (socket && socket->send(command_packet.get_data(), command_packet.get_data_length()));
706 }
707 
handshake()708 bool mysql_client::handshake() {
709     const char *data;
710     // recv greeting pakcet
711     if (sw_unlikely(!(data = recv_none_error_packet()))) {
712         return false;
713     }
714     mysql::greeting_packet greeting_packet(data);
715     // generate login packet
716     do {
717         mysql::login_packet login_packet(&greeting_packet, user, password, database, charset);
718         if (sw_unlikely(!send_packet(&login_packet))) {
719             return false;
720         }
721     } while (0);
722     // recv auth switch request packet, 4 possible packet types
723     switch (mysql::server_packet::parse_type(data = recv_packet())) {
724     case SW_MYSQL_PACKET_AUTH_SWITCH_REQUEST: {
725         mysql::auth_switch_request_packet request(data);
726         mysql::auth_switch_response_packet response(&request, password);
727         if (sw_unlikely(!send_packet(&response))) {
728             return false;
729         }
730         break;
731     }
732     case SW_MYSQL_PACKET_AUTH_SIGNATURE_REQUEST: {
733         mysql::auth_signature_request_packet request(data);
734         if (sw_unlikely(!request.is_vaild())) {
735             goto _proto_error;
736         }
737         if (sw_likely(!request.is_full_auth_required())) {
738             break;
739         }
740         // no cache, need full auth with rsa key (openssl required)
741 #ifdef SW_MYSQL_RSA_SUPPORT
742         // tell the server we are prepared
743         do {
744             mysql::auth_signature_prepared_packet prepared(request.header.number + 1);
745             if (sw_unlikely(!send_packet(&prepared))) {
746                 return false;
747             }
748         } while (0);
749         // recv rsa key and encode the password
750         do {
751             if (sw_unlikely(!(data = recv_none_error_packet()))) {
752                 return false;
753             }
754             mysql::raw_data_packet raw_data_packet(data);
755             mysql::auth_signature_response_packet response(
756                 &raw_data_packet, password, greeting_packet.auth_plugin_data);
757             if (sw_unlikely(!send_packet(&response))) {
758                 return false;
759             }
760         } while (0);
761         break;
762 #else
763         error_code = EPROTONOSUPPORT;
764         error_msg = SW_MYSQL_NO_RSA_ERROR;
765         return false;
766 #endif
767     }
768     case SW_MYSQL_PACKET_OK: {
769 #ifdef SW_LOG_TRACE_OPEN
770         mysql::ok_packet ok_packet(data);
771 #endif
772         return true;
773     }
774     case SW_MYSQL_PACKET_ERR:
775         server_error(data);
776         return false;
777     case SW_MYSQL_PACKET_NULL:
778         // io_error
779         return false;
780     default:
781     _proto_error:
782         proto_error(data, SW_MYSQL_PACKET_AUTH_SWITCH_REQUEST);
783         return false;
784     }
785     // maybe ok packet or err packet
786     if (sw_unlikely(!(data = recv_none_error_packet()))) {
787         return false;
788     }
789 #ifdef SW_LOG_TRACE_OPEN
790     mysql::ok_packet ok_packet(data);
791 #endif
792     return true;
793 }
794 
query(zval * return_value,const char * statement,size_t statement_length)795 void mysql_client::query(zval *return_value, const char *statement, size_t statement_length) {
796     send_query_request(return_value, statement, statement_length);
797     if (EXPECTED(!defer && Z_TYPE_P(return_value) == IS_TRUE)) {
798         recv_query_response(return_value);
799     }
800 }
801 
send_query_request(zval * return_value,const char * statement,size_t statement_length)802 void mysql_client::send_query_request(zval *return_value, const char *statement, size_t statement_length) {
803     if (sw_unlikely(!is_available_for_new_request())) {
804         RETURN_FALSE;
805     }
806     if (sw_unlikely(!send_command(SW_MYSQL_COM_QUERY, statement, statement_length))) {
807         RETURN_FALSE;
808     }
809     state = SW_MYSQL_STATE_QUERY;
810     RETURN_TRUE;
811 };
812 
recv_query_response(zval * return_value)813 void mysql_client::recv_query_response(zval *return_value) {
814     const char *data;
815     if (sw_unlikely(!(data = recv_none_error_packet()))) {
816         RETURN_FALSE;
817     }
818     if (mysql::server_packet::is_ok(data)) {
819         mysql::ok_packet ok_packet(data);
820         result.ok = ok_packet;
821         state = ok_packet.server_status.more_results_exists() ? SW_MYSQL_STATE_QUERY_MORE_RESULTS : SW_MYSQL_STATE_IDLE;
822         RETURN_TRUE;
823     }
824     do {
825         mysql::lcb_packet lcb_packet(data);
826         if (sw_unlikely(lcb_packet.length == 0)) {
827             // is it possible?
828             proto_error(data, SW_MYSQL_PACKET_FIELD);
829             RETURN_FALSE;
830         }
831         result.alloc_fields(lcb_packet.length);
832         for (uint32_t i = 0; i < lcb_packet.length; i++) {
833             if (sw_unlikely(!(data = recv_packet()))) {
834                 RETURN_FALSE;
835             }
836             result.set_field(i, data);
837         }
838     } while (0);
839     // expect eof
840     if (sw_unlikely(!(data = recv_eof_packet()))) {
841         RETURN_FALSE;
842     }
843     state = SW_MYSQL_STATE_QUERY_FETCH;
844     if (get_fetch_mode()) {
845         RETURN_TRUE;
846     }
847     fetch_all(return_value);
848 }
849 
handle_row_data_size(mysql::row_data * row_data,uint8_t size)850 const char *mysql_client::handle_row_data_size(mysql::row_data *row_data, uint8_t size) {
851     const char *p, *data;
852     SW_ASSERT(size < sizeof(row_data->stack_buffer));
853     if (sw_unlikely(!(p = row_data->read(size)))) {
854         uint8_t received = row_data->recv(row_data->stack_buffer, size);
855         if (sw_unlikely(!(data = recv_packet()))) {
856             return nullptr;
857         }
858         row_data->next_packet(data);
859         received += row_data->recv(row_data->stack_buffer + received, size - received);
860         if (sw_unlikely(received != size)) {
861             proto_error(data, SW_MYSQL_PACKET_ROW_DATA);
862             return nullptr;
863         }
864         p = row_data->stack_buffer;
865     }
866     return p;
867 }
868 
handle_row_data_lcb(mysql::row_data * row_data)869 bool mysql_client::handle_row_data_lcb(mysql::row_data *row_data) {
870     const char *p, *data;
871     // recv 1 byte to get binary code size
872     if (sw_unlikely(row_data->eof())) {
873         if (sw_unlikely(!(data = recv_packet()))) {
874             return false;
875         }
876         row_data->next_packet(data);
877         if (sw_unlikely(row_data->eof())) {
878             proto_error(data, SW_MYSQL_PACKET_ROW_DATA);
879             return false;
880         }
881     }
882     // decode lcb (use 0 to prevent read_ptr from moving)
883     // recv "size" bytes to get binary code length
884     p = handle_row_data_size(row_data, mysql::read_lcb_size(row_data->read(0)));
885     if (sw_unlikely(!p)) {
886         return false;
887     }
888     mysql::read_lcb(p, &row_data->text.length, &row_data->text.nul);
889     return true;
890 }
891 
handle_row_data_text(zval * return_value,mysql::row_data * row_data,mysql::field_packet * field)892 void mysql_client::handle_row_data_text(zval *return_value, mysql::row_data *row_data, mysql::field_packet *field) {
893     const char *p, *data;
894     if (sw_unlikely(!handle_row_data_lcb(row_data))) {
895         RETURN_FALSE;
896     }
897     if (sw_unlikely(!(p = row_data->read(row_data->text.length)))) {
898         size_t received = 0, required = row_data->text.length;
899         if (required < sizeof(row_data->stack_buffer)) {
900             p = handle_row_data_size(row_data, required);
901             if (sw_unlikely(!p)) {
902                 RETURN_FALSE;
903             }
904         } else {
905             zend_string *zstring = zend_string_alloc(required, 0);
906             do {
907                 received += row_data->recv(ZSTR_VAL(zstring) + received, required - received);
908                 if (received == required) {
909                     break;
910                 }
911                 if (row_data->eof()) {
912                     if (sw_unlikely(!(data = recv_packet()))) {
913                         RETURN_FALSE;
914                     }
915                     row_data->next_packet(data);
916                 }
917             } while (true);
918             ZSTR_VAL(zstring)[ZSTR_LEN(zstring)] = '\0';
919             RETVAL_STR(zstring);
920             goto _return;
921         }
922     }
923     if (row_data->text.nul || field->type == SW_MYSQL_TYPE_NULL) {
924         swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s is null", field->name_length, field->name);
925         RETURN_NULL();
926     } else {
927         RETVAL_STRINGL(p, row_data->text.length);
928     _return:
929         swoole_trace_log(SW_TRACE_MYSQL_CLIENT,
930                    "%.*s=[%lu]%.*s%s",
931                    field->name_length,
932                    field->name,
933                    Z_STRLEN_P(return_value),
934                    (int) SW_MIN(32, Z_STRLEN_P(return_value)),
935                    Z_STRVAL_P(return_value),
936                    (Z_STRLEN_P(return_value) > 32 ? "..." : ""));
937     }
938 }
939 
handle_strict_type(zval * ztext,mysql::field_packet * field)940 void mysql_client::handle_strict_type(zval *ztext, mysql::field_packet *field) {
941     if (sw_likely(Z_TYPE_P(ztext) == IS_STRING)) {
942         char *error;
943         switch (field->type) {
944         /* String */
945         case SW_MYSQL_TYPE_TINY_BLOB:
946         case SW_MYSQL_TYPE_MEDIUM_BLOB:
947         case SW_MYSQL_TYPE_LONG_BLOB:
948         case SW_MYSQL_TYPE_BLOB:
949         case SW_MYSQL_TYPE_DECIMAL:
950         case SW_MYSQL_TYPE_NEWDECIMAL:
951         case SW_MYSQL_TYPE_BIT:
952         case SW_MYSQL_TYPE_STRING:
953         case SW_MYSQL_TYPE_VAR_STRING:
954         case SW_MYSQL_TYPE_VARCHAR:
955         case SW_MYSQL_TYPE_NEWDATE:
956         /* Date Time */
957         case SW_MYSQL_TYPE_TIME:
958         case SW_MYSQL_TYPE_YEAR:
959         case SW_MYSQL_TYPE_TIMESTAMP:
960         case SW_MYSQL_TYPE_DATETIME:
961         case SW_MYSQL_TYPE_DATE:
962         case SW_MYSQL_TYPE_JSON:
963             return;
964         /* Integer */
965         case SW_MYSQL_TYPE_TINY:
966         case SW_MYSQL_TYPE_SHORT:
967         case SW_MYSQL_TYPE_INT24:
968         case SW_MYSQL_TYPE_LONG:
969             if (field->flags & SW_MYSQL_UNSIGNED_FLAG) {
970                 ulong_t uint = strtoul(Z_STRVAL_P(ztext), &error, 10);
971                 if (sw_likely(*error == '\0')) {
972                     zend_string_release(Z_STR_P(ztext));
973                     ZVAL_LONG(ztext, uint);
974                 }
975             } else {
976                 long sint = strtol(Z_STRVAL_P(ztext), &error, 10);
977                 if (sw_likely(*error == '\0')) {
978                     zend_string_release(Z_STR_P(ztext));
979                     ZVAL_LONG(ztext, sint);
980                 }
981             }
982             break;
983         case SW_MYSQL_TYPE_LONGLONG:
984             if (field->flags & SW_MYSQL_UNSIGNED_FLAG) {
985                 unsigned long long ubigint = strtoull(Z_STRVAL_P(ztext), &error, 10);
986                 if (sw_likely(*error == '\0' && ubigint <= ZEND_LONG_MAX)) {
987                     zend_string_release(Z_STR_P(ztext));
988                     ZVAL_LONG(ztext, ubigint);
989                 }
990             } else {
991                 long long sbigint = strtoll(Z_STRVAL_P(ztext), &error, 10);
992                 if (sw_likely(*error == '\0')) {
993                     zend_string_release(Z_STR_P(ztext));
994                     ZVAL_LONG(ztext, sbigint);
995                 }
996             }
997             break;
998         case SW_MYSQL_TYPE_FLOAT:
999         case SW_MYSQL_TYPE_DOUBLE: {
1000             double mdouble = strtod(Z_STRVAL_P(ztext), &error);
1001             if (sw_likely(*error == '\0')) {
1002                 zend_string_release(Z_STR_P(ztext));
1003                 ZVAL_DOUBLE(ztext, mdouble);
1004             }
1005             break;
1006         }
1007         default: {
1008             swoole_warning("unknown type[%d] for field [%.*s].", field->type, field->name_length, field->name);
1009             break;
1010         }
1011         }
1012     }
1013 }
1014 
fetch(zval * return_value)1015 void mysql_client::fetch(zval *return_value) {
1016     if (sw_unlikely(!is_connected())) {
1017         RETURN_FALSE;
1018     }
1019     if (sw_unlikely(state != SW_MYSQL_STATE_QUERY_FETCH)) {
1020         RETURN_NULL();
1021     }
1022     const char *data;
1023     if (sw_unlikely(!(data = recv_packet()))) {
1024         RETURN_FALSE;
1025     }
1026     if (mysql::server_packet::is_eof(data)) {
1027         mysql::eof_packet eof_packet(data);
1028         state =
1029             eof_packet.server_status.more_results_exists() ? SW_MYSQL_STATE_QUERY_MORE_RESULTS : SW_MYSQL_STATE_IDLE;
1030         RETURN_NULL();
1031     }
1032     do {
1033         mysql::row_data row_data(data);
1034         array_init_size(return_value, result.get_fields_length());
1035         for (uint32_t i = 0; i < result.get_fields_length(); i++) {
1036             mysql::field_packet *field = result.get_field(i);
1037             zval ztext;
1038             handle_row_data_text(&ztext, &row_data, field);
1039             if (sw_unlikely(Z_TYPE_P(&ztext) == IS_FALSE)) {
1040                 zval_ptr_dtor(return_value);
1041                 RETURN_FALSE;
1042             }
1043             if (strict_type) {
1044                 handle_strict_type(&ztext, field);
1045             }
1046             add_assoc_zval_ex(return_value, field->name, field->name_length, &ztext);
1047         }
1048     } while (0);
1049 }
1050 
fetch_all(zval * return_value)1051 void mysql_client::fetch_all(zval *return_value) {
1052     array_init(return_value);
1053     while (true) {
1054         zval zrow;
1055         fetch(&zrow);
1056         if (sw_unlikely(ZVAL_IS_NULL(&zrow))) {
1057             // eof
1058             return;
1059         }
1060         if (sw_unlikely(Z_TYPE_P(&zrow) == IS_FALSE)) {
1061             // error
1062             zval_ptr_dtor(return_value);
1063             RETURN_FALSE;
1064         }
1065         (void) add_next_index_zval(return_value, &zrow);
1066     }
1067 }
1068 
next_result(zval * return_value)1069 void mysql_client::next_result(zval *return_value) {
1070     if (sw_unlikely(state == SW_MYSQL_STATE_QUERY_FETCH)) {
1071         // skip unread data
1072         fetch_all(return_value);
1073         zval_ptr_dtor(return_value);
1074         next_result(return_value);
1075     } else if (sw_likely(state == SW_MYSQL_STATE_QUERY_MORE_RESULTS)) {
1076         recv_query_response(return_value);
1077     } else if (state == SW_MYSQL_STATE_IDLE) {
1078         RETURN_NULL();
1079     } else {
1080         RETURN_FALSE;
1081     }
1082 }
1083 
send_prepare_request(const char * statement,size_t statement_length)1084 bool mysql_client::send_prepare_request(const char *statement, size_t statement_length) {
1085     this->statement = new mysql_statement(this, statement, statement_length);
1086     if (sw_unlikely(!this->statement->send_prepare_request())) {
1087         delete this->statement;
1088         this->statement = nullptr;
1089         return false;
1090     }
1091     return true;
1092 }
1093 
recv_prepare_response()1094 mysql_statement *mysql_client::recv_prepare_response() {
1095     if (sw_likely(state == SW_MYSQL_STATE_PREPARE)) {
1096         mysql_statement *statement = this->statement;
1097         SW_ASSERT(statement != nullptr);
1098         this->statement = nullptr;
1099         if (sw_unlikely(!statement->recv_prepare_response())) {
1100             delete statement;
1101             return nullptr;
1102         }
1103         statements[statement->info.id] = statement;
1104         return statement;
1105     }
1106     return nullptr;
1107 }
1108 
close()1109 void mysql_client::close() {
1110     state = SW_MYSQL_STATE_CLOSED;
1111     Socket *socket = this->socket;
1112     if (socket) {
1113         del_timeout_controller();
1114         if (!quit && is_writable()) {
1115             send_command_without_check(SW_MYSQL_COM_QUIT);
1116             quit = true;
1117         }
1118         // make statements non-available
1119         while (!statements.empty()) {
1120             auto i = statements.begin();
1121             i->second->close(false);
1122             statements.erase(i);
1123         }
1124         if (sw_likely(!socket->has_bound())) {
1125             this->socket = nullptr;
1126         }
1127         if (sw_likely(socket->close())) {
1128             delete socket;
1129         }
1130     }
1131 }
1132 
send_prepare_request()1133 bool mysql_statement::send_prepare_request() {
1134     if (sw_unlikely(!is_available_for_new_request())) {
1135         return false;
1136     }
1137     if (sw_unlikely(!client->send_command(SW_MYSQL_COM_STMT_PREPARE, statement.c_str(), statement.length()))) {
1138         return false;
1139     }
1140     client->state = SW_MYSQL_STATE_PREPARE;
1141     return true;
1142 }
1143 
recv_prepare_response()1144 bool mysql_statement::recv_prepare_response() {
1145     if (sw_unlikely(!is_available())) {
1146         return false;
1147     } else {
1148         client->state = SW_MYSQL_STATE_IDLE;
1149     }
1150     const char *data;
1151     if (sw_unlikely(!(data = client->recv_none_error_packet()))) {
1152         return false;
1153     }
1154     info = mysql::statement(data);
1155     if (sw_likely(info.param_count != 0)) {
1156         for (uint16_t i = info.param_count; i--;) {
1157             if (sw_unlikely(!(data = client->recv_packet()))) {
1158                 return false;
1159             }
1160 #ifdef SW_LOG_TRACE_OPEN
1161             mysql::param_packet param_packet(data);
1162 #endif
1163         }
1164         if (sw_unlikely(!(data = client->recv_eof_packet()))) {
1165             return false;
1166         }
1167     }
1168     if (info.field_count != 0) {
1169         result.alloc_fields(info.field_count);
1170         for (uint16_t i = 0; i < info.field_count; i++) {
1171             if (sw_unlikely(!(data = client->recv_packet()))) {
1172                 return false;
1173             }
1174             result.set_field(i, data);
1175         }
1176         if (sw_unlikely(!(data = client->recv_eof_packet()))) {
1177             return false;
1178         }
1179     }
1180     return true;
1181 }
1182 
execute(zval * return_value,zval * params)1183 void mysql_statement::execute(zval *return_value, zval *params) {
1184     send_execute_request(return_value, params);
1185     /* Notice: must check return_value first */
1186     if (EXPECTED(Z_TYPE_P(return_value) == IS_TRUE && !client->get_defer())) {
1187         recv_execute_response(return_value);
1188     }
1189 }
1190 
send_execute_request(zval * return_value,zval * params)1191 void mysql_statement::send_execute_request(zval *return_value, zval *params) {
1192     if (sw_unlikely(!is_available_for_new_request())) {
1193         RETURN_FALSE;
1194     }
1195 
1196     uint32_t param_count = params ? php_swoole_array_length(params) : 0;
1197 
1198     if (sw_unlikely(param_count != info.param_count)) {
1199         client->non_sql_error(MYSQLND_CR_INVALID_PARAMETER_NO,
1200                               "Statement#%u expects %u parameter, %u given.",
1201                               info.id,
1202                               info.param_count,
1203                               param_count);
1204         RETURN_FALSE;
1205     }
1206 
1207     String *buffer = client->socket->get_write_buffer();
1208     char *p = buffer->str;
1209 
1210     memset(p, 0, 5);
1211     // command
1212     buffer->str[4] = SW_MYSQL_COM_STMT_EXECUTE;
1213     buffer->length = 5;
1214     p += 5;
1215 
1216     // stmt.id
1217     sw_mysql_int4store(p, info.id);
1218     p += 4;
1219     // flags = CURSOR_TYPE_NO_CURSOR
1220     sw_mysql_int1store(p, 0);
1221     p += 1;
1222     // iteration_count
1223     sw_mysql_int4store(p, 1);
1224     p += 4;
1225     buffer->length += 9;
1226 
1227     // TODO: support more types
1228     if (param_count != 0) {
1229         // null bitmap
1230         size_t null_start_offset = p - buffer->str;
1231         unsigned int map_size = (param_count + 7) / 8;
1232         memset(p, 0, map_size);
1233         p += map_size;
1234         buffer->length += map_size;
1235 
1236         // rebind
1237         sw_mysql_int1store(p, 1);
1238         p += 1;
1239         buffer->length += 1;
1240 
1241         size_t type_start_offset = p - buffer->str;
1242         p += param_count * 2;
1243         buffer->length += param_count * 2;
1244 
1245         char stack_buffer[10];
1246         zend_ulong index = 0;
1247         zval *value;
1248         ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(params), value) {
1249             switch (client->strict_type ? Z_TYPE_P(value) : (IS_NULL == Z_TYPE_P(value) ? IS_NULL : IS_STRING)) {
1250             case IS_NULL:
1251                 *((buffer->str + null_start_offset) + (index / 8)) |= (1UL << (index % 8));
1252                 sw_mysql_int2store((buffer->str + type_start_offset) + (index * 2), SW_MYSQL_TYPE_NULL);
1253                 break;
1254             case IS_TRUE:
1255             case IS_FALSE:
1256             case IS_LONG:
1257                 sw_mysql_int2store((buffer->str + type_start_offset) + (index * 2), SW_MYSQL_TYPE_LONGLONG);
1258                 sw_mysql_int8store(stack_buffer, zval_get_long(value));
1259                 if (buffer->append(stack_buffer, mysql::get_static_type_size(SW_MYSQL_TYPE_LONGLONG)) < 0) {
1260                     RETURN_FALSE;
1261                 }
1262                 break;
1263             case IS_DOUBLE:
1264                 sw_mysql_int2store((buffer->str + type_start_offset) + (index * 2), SW_MYSQL_TYPE_DOUBLE);
1265                 sw_mysql_doublestore(stack_buffer, zval_get_double(value));
1266                 if (buffer->append(stack_buffer, mysql::get_static_type_size(SW_MYSQL_TYPE_DOUBLE)) < 0) {
1267                     RETURN_FALSE;
1268                 }
1269                 break;
1270             default:
1271                 zend::String str_value(value);
1272                 uint8_t lcb_size = mysql::write_lcb(stack_buffer, str_value.len());
1273                 sw_mysql_int2store((buffer->str + type_start_offset) + (index * 2), SW_MYSQL_TYPE_VAR_STRING);
1274                 if (buffer->append(stack_buffer, lcb_size) < 0) {
1275                     RETURN_FALSE;
1276                 }
1277                 if (buffer->append(str_value.val(), str_value.len()) < 0) {
1278                     RETURN_FALSE;
1279                 }
1280             }
1281             index++;
1282         }
1283         ZEND_HASH_FOREACH_END();
1284     }
1285     do {
1286         size_t length = buffer->length - SW_MYSQL_PACKET_HEADER_SIZE;
1287         size_t send_s = SW_MIN(length, SW_MYSQL_MAX_PACKET_BODY_SIZE);
1288         mysql::packet::set_header(buffer->str, send_s, 0);
1289         if (sw_unlikely(!client->send_raw(buffer->str, SW_MYSQL_PACKET_HEADER_SIZE + send_s))) {
1290             RETURN_FALSE;
1291         }
1292         if (sw_unlikely(length > SW_MYSQL_MAX_PACKET_BODY_SIZE)) {
1293             size_t send_n = SW_MYSQL_MAX_PACKET_BODY_SIZE, number = 1;
1294             /* MySQL single packet size is 16M, we must subpackage */
1295             while (send_n < length) {
1296                 send_s = length - send_n;
1297                 send_s = SW_MIN(send_s, SW_MYSQL_MAX_PACKET_BODY_SIZE);
1298                 mysql::packet::set_header(buffer->str, send_s, number++);
1299                 if (sw_unlikely(!client->send_raw(buffer->str, SW_MYSQL_PACKET_HEADER_SIZE)) ||
1300                     !client->send_raw(buffer->str + SW_MYSQL_PACKET_HEADER_SIZE + send_n, send_s)) {
1301                     RETURN_FALSE;
1302                 }
1303                 send_n += send_s;
1304             }
1305         }
1306     } while (0);
1307     client->state = SW_MYSQL_STATE_EXECUTE;
1308     RETURN_TRUE;
1309 }
1310 
recv_execute_response(zval * return_value)1311 void mysql_statement::recv_execute_response(zval *return_value) {
1312     if (sw_unlikely(!is_available())) {
1313         RETURN_FALSE;
1314     }
1315     const char *data;
1316     if (sw_unlikely(!(data = client->recv_none_error_packet()))) {
1317         RETURN_FALSE;
1318     }
1319     if (mysql::server_packet::is_ok(data)) {
1320         mysql::ok_packet ok_packet(data);
1321         result.ok = ok_packet;
1322         client->state =
1323             ok_packet.server_status.more_results_exists() ? SW_MYSQL_STATE_EXECUTE_MORE_RESULTS : SW_MYSQL_STATE_IDLE;
1324         RETURN_TRUE;
1325     }
1326     do {
1327         mysql::lcb_packet lcb_packet(data);
1328         if (sw_unlikely(lcb_packet.length == 0)) {
1329             // is it possible?
1330             client->proto_error(data, SW_MYSQL_PACKET_FIELD);
1331             RETURN_FALSE;
1332         }
1333         // although we have already known the field data when we prepared the statement,
1334         // we don't know if the data is always reliable, such as when we using stored procedure...
1335         // so we should not optimize here for the time being for stability
1336         result.alloc_fields(lcb_packet.length);
1337         for (size_t i = 0; i < result.get_fields_length(); i++) {
1338             if (sw_unlikely(!(data = client->recv_packet()))) {
1339                 RETURN_FALSE;
1340             }
1341             result.set_field(i, data);
1342         }
1343     } while (0);
1344     // expect eof
1345     if (sw_unlikely(!(data = client->recv_eof_packet()))) {
1346         RETURN_FALSE;
1347     }
1348     client->state = SW_MYSQL_STATE_EXECUTE_FETCH;
1349     if (client->get_fetch_mode()) {
1350         RETURN_TRUE;
1351     }
1352     fetch_all(return_value);
1353 }
1354 
fetch(zval * return_value)1355 void mysql_statement::fetch(zval *return_value) {
1356     if (sw_unlikely(!is_available())) {
1357         RETURN_FALSE;
1358     }
1359     if (sw_unlikely(client->state != SW_MYSQL_STATE_EXECUTE_FETCH)) {
1360         RETURN_NULL();
1361     }
1362     const char *data;
1363     if (sw_unlikely(!(data = client->recv_packet()))) {
1364         RETURN_FALSE;
1365     }
1366     if (mysql::server_packet::is_eof(data)) {
1367         mysql::eof_packet eof_packet(data);
1368         client->state =
1369             eof_packet.server_status.more_results_exists() ? SW_MYSQL_STATE_EXECUTE_MORE_RESULTS : SW_MYSQL_STATE_IDLE;
1370         RETURN_NULL();
1371     }
1372     do {
1373         mysql::row_data row_data(data);
1374         uint32_t null_bitmap_size = mysql::null_bitmap::get_size(result.get_fields_length());
1375         mysql::null_bitmap null_bitmap(row_data.read(null_bitmap_size), null_bitmap_size);
1376 
1377         array_init_size(return_value, result.get_fields_length());
1378         for (uint32_t i = 0; i < result.get_fields_length(); i++) {
1379             mysql::field_packet *field = result.get_field(i);
1380 
1381             /* to check Null-Bitmap @see https://dev.mysql.com/doc/internals/en/null-bitmap.html */
1382             if (null_bitmap.is_null(i) || field->type == SW_MYSQL_TYPE_NULL) {
1383                 swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s is null", field->name_length, field->name);
1384                 add_assoc_null_ex(return_value, field->name, field->name_length);
1385                 continue;
1386             }
1387 
1388             switch (field->type) {
1389             /* String */
1390             case SW_MYSQL_TYPE_TINY_BLOB:
1391             case SW_MYSQL_TYPE_MEDIUM_BLOB:
1392             case SW_MYSQL_TYPE_LONG_BLOB:
1393             case SW_MYSQL_TYPE_BLOB:
1394             case SW_MYSQL_TYPE_DECIMAL:
1395             case SW_MYSQL_TYPE_NEWDECIMAL:
1396             case SW_MYSQL_TYPE_BIT:
1397             case SW_MYSQL_TYPE_JSON:
1398             case SW_MYSQL_TYPE_STRING:
1399             case SW_MYSQL_TYPE_VAR_STRING:
1400             case SW_MYSQL_TYPE_VARCHAR:
1401             case SW_MYSQL_TYPE_NEWDATE: {
1402             _add_string:
1403                 zval ztext;
1404                 client->handle_row_data_text(&ztext, &row_data, field);
1405                 if (sw_unlikely(Z_TYPE_P(&ztext) == IS_FALSE)) {
1406                     zval_ptr_dtor(return_value);
1407                     RETURN_FALSE;
1408                 }
1409                 add_assoc_zval_ex(return_value, field->name, field->name_length, &ztext);
1410                 break;
1411             }
1412             default: {
1413                 const char *p = nullptr;
1414                 uint8_t lcb = mysql::get_static_type_size(field->type);
1415                 if (lcb == 0) {
1416                     client->handle_row_data_lcb(&row_data);
1417                     lcb = row_data.text.length;
1418                 }
1419                 p = client->handle_row_data_size(&row_data, lcb);
1420                 if (sw_unlikely(!p)) {
1421                     zval_ptr_dtor(return_value);
1422                     RETURN_FALSE;
1423                 }
1424                 /* Date Time */
1425                 switch (field->type) {
1426                 case SW_MYSQL_TYPE_TIMESTAMP:
1427                 case SW_MYSQL_TYPE_DATETIME: {
1428                     std::string datetime = mysql::datetime(p, row_data.text.length, field->decimals);
1429                     add_assoc_stringl_ex(
1430                         return_value, field->name, field->name_length, (char *) datetime.c_str(), datetime.length());
1431                     swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%s", field->name_length, field->name, datetime.c_str());
1432                     break;
1433                 }
1434                 case SW_MYSQL_TYPE_TIME: {
1435                     std::string time = mysql::time(p, row_data.text.length, field->decimals);
1436                     add_assoc_stringl_ex(
1437                         return_value, field->name, field->name_length, (char *) time.c_str(), time.length());
1438                     swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%s", field->name_length, field->name, time.c_str());
1439                     break;
1440                 }
1441                 case SW_MYSQL_TYPE_DATE: {
1442                     std::string date = mysql::date(p, row_data.text.length);
1443                     add_assoc_stringl_ex(
1444                         return_value, field->name, field->name_length, (char *) date.c_str(), date.length());
1445                     swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%s", field->name_length, field->name, date.c_str());
1446                     break;
1447                 }
1448                 case SW_MYSQL_TYPE_YEAR: {
1449                     add_assoc_long_ex(return_value, field->name, field->name_length, sw_mysql_uint2korr2korr(p));
1450                     swoole_trace_log(
1451                         SW_TRACE_MYSQL_CLIENT, "%.*s=%d", field->name_length, field->name, sw_mysql_uint2korr2korr(p));
1452                     break;
1453                 }
1454                 /* Number */
1455                 case SW_MYSQL_TYPE_TINY:
1456                     if (field->flags & SW_MYSQL_UNSIGNED_FLAG) {
1457                         add_assoc_long_ex(return_value, field->name, field->name_length, *(uint8_t *) p);
1458                         swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%u", field->name_length, field->name, *(uint8_t *) p);
1459                     } else {
1460                         add_assoc_long_ex(return_value, field->name, field->name_length, *(int8_t *) p);
1461                         swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%d", field->name_length, field->name, *(int8_t *) p);
1462                     }
1463                     break;
1464                 case SW_MYSQL_TYPE_SHORT:
1465                     if (field->flags & SW_MYSQL_UNSIGNED_FLAG) {
1466                         add_assoc_long_ex(return_value, field->name, field->name_length, *(uint16_t *) p);
1467                         swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%u", field->name_length, field->name, *(uint16_t *) p);
1468                     } else {
1469                         add_assoc_long_ex(return_value, field->name, field->name_length, *(int16_t *) p);
1470                         swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%d", field->name_length, field->name, *(int16_t *) p);
1471                     }
1472                     break;
1473                 case SW_MYSQL_TYPE_INT24:
1474                 case SW_MYSQL_TYPE_LONG:
1475                     if (field->flags & SW_MYSQL_UNSIGNED_FLAG) {
1476                         add_assoc_long_ex(return_value, field->name, field->name_length, *(uint32_t *) p);
1477                         swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%u", field->name_length, field->name, *(uint32_t *) p);
1478                     } else {
1479                         add_assoc_long_ex(return_value, field->name, field->name_length, *(int32_t *) p);
1480                         swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%d", field->name_length, field->name, *(int32_t *) p);
1481                     }
1482                     break;
1483                 case SW_MYSQL_TYPE_LONGLONG:
1484                     if (field->flags & SW_MYSQL_UNSIGNED_FLAG) {
1485                         add_assoc_ulong_safe_ex(return_value, field->name, field->name_length, *(uint64_t *) p);
1486                         swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%lu", field->name_length, field->name, *(uint64_t *) p);
1487                     } else {
1488                         add_assoc_long_ex(return_value, field->name, field->name_length, *(int64_t *) p);
1489                         swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%ld", field->name_length, field->name, *(int64_t *) p);
1490                     }
1491                     break;
1492                 case SW_MYSQL_TYPE_FLOAT: {
1493                     double dv = sw_php_math_round(*(float *) p, 5, PHP_ROUND_HALF_DOWN);
1494                     add_assoc_double_ex(return_value, field->name, field->name_length, dv);
1495                     swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%.7f", field->name_length, field->name, dv);
1496                 } break;
1497                 case SW_MYSQL_TYPE_DOUBLE: {
1498                     add_assoc_double_ex(return_value, field->name, field->name_length, *(double *) p);
1499                     swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%.16f", field->name_length, field->name, *(double *) p);
1500                 } break;
1501                 default:
1502                     swoole_warning("unknown type[%d] for field [%.*s].", field->type, field->name_length, field->name);
1503                     goto _add_string;
1504                 }
1505             }
1506             }
1507         }
1508     } while (0);
1509 }
1510 
fetch_all(zval * return_value)1511 void mysql_statement::fetch_all(zval *return_value) {
1512     if (sw_unlikely(!is_available())) {
1513         RETURN_FALSE;
1514     }
1515 
1516     zval zrow;
1517     array_init(return_value);
1518     while (true) {
1519         fetch(&zrow);
1520         if (sw_unlikely(ZVAL_IS_NULL(&zrow))) {
1521             // eof
1522             return;
1523         }
1524         if (sw_unlikely(Z_TYPE_P(&zrow) == IS_FALSE)) {
1525             // error
1526             zval_ptr_dtor(return_value);
1527             RETURN_FALSE;
1528         }
1529         (void) add_next_index_zval(return_value, &zrow);
1530     }
1531 }
1532 
next_result(zval * return_value)1533 void mysql_statement::next_result(zval *return_value) {
1534     if (sw_unlikely(!is_available())) {
1535         RETURN_FALSE;
1536     }
1537     if (sw_unlikely(client->state == SW_MYSQL_STATE_EXECUTE_FETCH)) {
1538         // skip unread data
1539         fetch_all(return_value);
1540         zval_ptr_dtor(return_value);
1541         next_result(return_value);
1542     } else if (sw_likely(client->state == SW_MYSQL_STATE_EXECUTE_MORE_RESULTS)) {
1543         recv_execute_response(return_value);
1544     } else if (client->state == SW_MYSQL_STATE_IDLE) {
1545         RETURN_NULL();
1546     } else {
1547         RETURN_FALSE;
1548     }
1549 }
1550 
php_swoole_mysql_coro_fetch_object(zend_object * obj)1551 static sw_inline mysql_coro_t *php_swoole_mysql_coro_fetch_object(zend_object *obj) {
1552     return (mysql_coro_t *) ((char *) obj - swoole_mysql_coro_handlers.offset);
1553 }
1554 
php_swoole_get_mysql_client(zval * zobject)1555 static sw_inline mysql_client *php_swoole_get_mysql_client(zval *zobject) {
1556     return php_swoole_mysql_coro_fetch_object(Z_OBJ_P(zobject))->client;
1557 }
1558 
php_swoole_mysql_coro_free_object(zend_object * object)1559 static void php_swoole_mysql_coro_free_object(zend_object *object) {
1560     mysql_coro_t *zmc = php_swoole_mysql_coro_fetch_object(object);
1561     delete zmc->client;
1562     zend_object_std_dtor(&zmc->std);
1563 }
1564 
php_swoole_mysql_coro_create_object(zend_class_entry * ce)1565 static zend_object *php_swoole_mysql_coro_create_object(zend_class_entry *ce) {
1566     mysql_coro_t *zmc = (mysql_coro_t *) zend_object_alloc(sizeof(mysql_coro_t), ce);
1567     zend_object_std_init(&zmc->std, ce);
1568     object_properties_init(&zmc->std, ce);
1569     zmc->std.handlers = &swoole_mysql_coro_handlers;
1570     zmc->client = new mysql_client;
1571     return &zmc->std;
1572 }
1573 
php_swoole_mysql_coro_statement_fetch_object(zend_object * obj)1574 static sw_inline mysql_coro_statement_t *php_swoole_mysql_coro_statement_fetch_object(zend_object *obj) {
1575     return (mysql_coro_statement_t *) ((char *) obj - swoole_mysql_coro_statement_handlers.offset);
1576 }
1577 
php_swoole_get_mysql_statement(zval * zobject)1578 static sw_inline mysql_statement *php_swoole_get_mysql_statement(zval *zobject) {
1579     return php_swoole_mysql_coro_statement_fetch_object(Z_OBJ_P(zobject))->statement;
1580 }
1581 
php_swoole_mysql_coro_statement_free_object(zend_object * object)1582 static void php_swoole_mysql_coro_statement_free_object(zend_object *object) {
1583     mysql_coro_statement_t *zms = php_swoole_mysql_coro_statement_fetch_object(object);
1584     delete zms->statement;
1585     OBJ_RELEASE(zms->zclient);
1586     zend_object_std_dtor(&zms->std);
1587 }
1588 
php_swoole_mysql_coro_statement_create_object(zend_class_entry * ce,mysql_statement * statement,zend_object * client)1589 static sw_inline zend_object *php_swoole_mysql_coro_statement_create_object(zend_class_entry *ce,
1590                                                                             mysql_statement *statement,
1591                                                                             zend_object *client) {
1592     zval zobject;
1593     mysql_coro_statement_t *zms = (mysql_coro_statement_t *) zend_object_alloc(sizeof(mysql_coro_statement_t), ce);
1594     zend_object_std_init(&zms->std, ce);
1595     object_properties_init(&zms->std, ce);
1596     zms->std.handlers = &swoole_mysql_coro_statement_handlers;
1597     ZVAL_OBJ(&zobject, &zms->std);
1598     zend_update_property_long(ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("id"), statement->info.id);
1599     zms->statement = statement;
1600     zms->zclient = client;
1601     GC_ADDREF(client);
1602     return &zms->std;
1603 }
1604 
php_swoole_mysql_coro_statement_create_object(mysql_statement * statement,zend_object * client)1605 static sw_inline zend_object *php_swoole_mysql_coro_statement_create_object(mysql_statement *statement,
1606                                                                             zend_object *client) {
1607     return php_swoole_mysql_coro_statement_create_object(swoole_mysql_coro_statement_ce, statement, client);
1608 }
1609 
php_swoole_mysql_coro_statement_create_object(zend_class_entry * ce)1610 static zend_object *php_swoole_mysql_coro_statement_create_object(zend_class_entry *ce) {
1611     php_swoole_fatal_error(E_ERROR, "you must create mysql statement object by prepare method");
1612     return nullptr;
1613 }
1614 
swoole_mysql_coro_sync_error_properties(zval * zobject,int error_code,const char * error_msg,const bool connected=true)1615 static sw_inline void swoole_mysql_coro_sync_error_properties(zval *zobject,
1616                                                               int error_code,
1617                                                               const char *error_msg,
1618                                                               const bool connected = true) {
1619     SW_ASSERT(instanceof_function(Z_OBJCE_P(zobject), swoole_mysql_coro_ce) ||
1620               instanceof_function(Z_OBJCE_P(zobject), swoole_mysql_coro_statement_ce));
1621     zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errno"), error_code);
1622     zend_update_property_string(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("error"), error_msg);
1623     if (!connected) {
1624         zend_update_property_bool(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), connected);
1625     }
1626 }
1627 
swoole_mysql_coro_sync_query_result_properties(zval * zobject,mysql_client * mc,zval * return_value)1628 static sw_inline void swoole_mysql_coro_sync_query_result_properties(zval *zobject,
1629                                                                      mysql_client *mc,
1630                                                                      zval *return_value) {
1631     switch (Z_TYPE_P(return_value)) {
1632     case IS_TRUE: {
1633         mysql::ok_packet *ok_packet = &mc->result.ok;
1634         zend_update_property_long(
1635             Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("affected_rows"), ok_packet->affected_rows);
1636         zend_update_property_long(
1637             Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("insert_id"), ok_packet->last_insert_id);
1638         break;
1639     }
1640     case IS_FALSE: {
1641         swoole_mysql_coro_sync_error_properties(zobject, mc->get_error_code(), mc->get_error_msg());
1642         break;
1643     }
1644     default:
1645         break;
1646     }
1647 }
1648 
swoole_mysql_coro_sync_execute_error_properties(zval * zobject,int error_code,const char * error_msg,const bool connected=true)1649 static sw_inline void swoole_mysql_coro_sync_execute_error_properties(zval *zobject,
1650                                                                       int error_code,
1651                                                                       const char *error_msg,
1652                                                                       const bool connected = true) {
1653     swoole_mysql_coro_sync_error_properties(zobject, error_code, error_msg, connected);
1654 
1655     /* backward compatibility (sync error info to client) */
1656     zval zclient;
1657     ZVAL_OBJ(&zclient, php_swoole_mysql_coro_statement_fetch_object(Z_OBJ_P(zobject))->zclient);
1658     swoole_mysql_coro_sync_error_properties(&zclient, error_code, error_msg, connected);
1659 }
1660 
swoole_mysql_coro_sync_execute_result_properties(zval * zobject,zval * return_value)1661 static sw_inline void swoole_mysql_coro_sync_execute_result_properties(zval *zobject, zval *return_value) {
1662     mysql_coro_statement_t *zms = php_swoole_mysql_coro_statement_fetch_object(Z_OBJ_P(zobject));
1663     mysql_statement *ms = zms->statement;
1664 
1665     switch (Z_TYPE_P(return_value)) {
1666     case IS_TRUE: {
1667         mysql::ok_packet *ok_packet = &ms->result.ok;
1668         zend_update_property_long(
1669             Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("affected_rows"), ok_packet->affected_rows);
1670         zend_update_property_long(
1671             Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("insert_id"), ok_packet->last_insert_id);
1672 
1673         /* backward compatibility (sync result info to client) */
1674         zval zclient;
1675         ZVAL_OBJ(&zclient, zms->zclient);
1676         zend_update_property_long(
1677             Z_OBJCE_P(&zclient), SW_Z8_OBJ_P(&zclient), ZEND_STRL("affected_rows"), ok_packet->affected_rows);
1678         zend_update_property_long(
1679             Z_OBJCE_P(&zclient), SW_Z8_OBJ_P(&zclient), ZEND_STRL("insert_id"), ok_packet->last_insert_id);
1680         break;
1681     }
1682     case IS_FALSE: {
1683         swoole_mysql_coro_sync_execute_error_properties(zobject, ms->get_error_code(), ms->get_error_msg());
1684         break;
1685     }
1686     default:
1687         break;
1688     }
1689 }
1690 
php_swoole_mysql_coro_minit(int module_number)1691 void php_swoole_mysql_coro_minit(int module_number) {
1692     SW_INIT_CLASS_ENTRY(swoole_mysql_coro, "Swoole\\Coroutine\\MySQL", nullptr, "Co\\MySQL", swoole_mysql_coro_methods);
1693     SW_SET_CLASS_NOT_SERIALIZABLE(swoole_mysql_coro);
1694     SW_SET_CLASS_CLONEABLE(swoole_mysql_coro, sw_zend_class_clone_deny);
1695     SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_mysql_coro, sw_zend_class_unset_property_deny);
1696     SW_SET_CLASS_CUSTOM_OBJECT(
1697         swoole_mysql_coro, php_swoole_mysql_coro_create_object, php_swoole_mysql_coro_free_object, mysql_coro_t, std);
1698 
1699     SW_INIT_CLASS_ENTRY(swoole_mysql_coro_statement,
1700                         "Swoole\\Coroutine\\MySQL\\Statement",
1701                         nullptr,
1702                         "Co\\MySQL\\Statement",
1703                         swoole_mysql_coro_statement_methods);
1704     SW_SET_CLASS_NOT_SERIALIZABLE(swoole_mysql_coro_statement);
1705     SW_SET_CLASS_CLONEABLE(swoole_mysql_coro_statement, sw_zend_class_clone_deny);
1706     SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_mysql_coro_statement, sw_zend_class_unset_property_deny);
1707     SW_SET_CLASS_CUSTOM_OBJECT(swoole_mysql_coro_statement,
1708                                php_swoole_mysql_coro_statement_create_object,
1709                                php_swoole_mysql_coro_statement_free_object,
1710                                mysql_coro_statement_t,
1711                                std);
1712 
1713     SW_INIT_CLASS_ENTRY_EX(swoole_mysql_coro_exception,
1714                            "Swoole\\Coroutine\\MySQL\\Exception",
1715                            nullptr,
1716                            "Co\\MySQL\\Exception",
1717                            nullptr,
1718                            swoole_exception);
1719     SW_SET_CLASS_NOT_SERIALIZABLE(swoole_mysql_coro_exception);
1720     SW_SET_CLASS_CLONEABLE(swoole_mysql_coro_exception, sw_zend_class_clone_deny);
1721     SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_mysql_coro_exception, sw_zend_class_unset_property_deny);
1722     SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_mysql_coro_exception);
1723 
1724     zend_declare_property_null(swoole_mysql_coro_ce, ZEND_STRL("serverInfo"), ZEND_ACC_PUBLIC);
1725     zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("sock"), -1, ZEND_ACC_PUBLIC);
1726     zend_declare_property_bool(swoole_mysql_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC);
1727     zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("connect_errno"), 0, ZEND_ACC_PUBLIC);
1728     zend_declare_property_string(swoole_mysql_coro_ce, ZEND_STRL("connect_error"), "", ZEND_ACC_PUBLIC);
1729     zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("affected_rows"), 0, ZEND_ACC_PUBLIC);
1730     zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("insert_id"), 0, ZEND_ACC_PUBLIC);
1731     zend_declare_property_string(swoole_mysql_coro_ce, ZEND_STRL("error"), "", ZEND_ACC_PUBLIC);
1732     zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("errno"), 0, ZEND_ACC_PUBLIC);
1733 
1734     zend_declare_property_long(swoole_mysql_coro_statement_ce, ZEND_STRL("id"), 0, ZEND_ACC_PUBLIC);
1735     zend_declare_property_long(swoole_mysql_coro_statement_ce, ZEND_STRL("affected_rows"), 0, ZEND_ACC_PUBLIC);
1736     zend_declare_property_long(swoole_mysql_coro_statement_ce, ZEND_STRL("insert_id"), 0, ZEND_ACC_PUBLIC);
1737     zend_declare_property_string(swoole_mysql_coro_statement_ce, ZEND_STRL("error"), "", ZEND_ACC_PUBLIC);
1738     zend_declare_property_long(swoole_mysql_coro_statement_ce, ZEND_STRL("errno"), 0, ZEND_ACC_PUBLIC);
1739 
1740     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_UNKNOWN_ERROR", MYSQLND_CR_UNKNOWN_ERROR);
1741     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_CONNECTION_ERROR", MYSQLND_CR_CONNECTION_ERROR);
1742     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_SERVER_GONE_ERROR", MYSQLND_CR_SERVER_GONE_ERROR);
1743     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_OUT_OF_MEMORY", MYSQLND_CR_OUT_OF_MEMORY);
1744     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_SERVER_LOST", MYSQLND_CR_SERVER_LOST);
1745     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_COMMANDS_OUT_OF_SYNC", MYSQLND_CR_COMMANDS_OUT_OF_SYNC);
1746     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_CANT_FIND_CHARSET", MYSQLND_CR_CANT_FIND_CHARSET);
1747     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_MALFORMED_PACKET", MYSQLND_CR_MALFORMED_PACKET);
1748     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_NOT_IMPLEMENTED", MYSQLND_CR_NOT_IMPLEMENTED);
1749     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_NO_PREPARE_STMT", MYSQLND_CR_NO_PREPARE_STMT);
1750     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_PARAMS_NOT_BOUND", MYSQLND_CR_PARAMS_NOT_BOUND);
1751     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_INVALID_PARAMETER_NO", MYSQLND_CR_INVALID_PARAMETER_NO);
1752     SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_INVALID_BUFFER_USE", MYSQLND_CR_INVALID_BUFFER_USE);
1753 }
1754 
PHP_METHOD(swoole_mysql_coro,__construct)1755 static PHP_METHOD(swoole_mysql_coro, __construct) {}
PHP_METHOD(swoole_mysql_coro,__destruct)1756 static PHP_METHOD(swoole_mysql_coro, __destruct) {}
1757 
PHP_METHOD(swoole_mysql_coro,connect)1758 static PHP_METHOD(swoole_mysql_coro, connect) {
1759     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
1760     zval *zserver_info = nullptr;
1761 
1762     ZEND_PARSE_PARAMETERS_START(0, 1)
1763     Z_PARAM_OPTIONAL
1764     Z_PARAM_ARRAY_EX(zserver_info, 1, 0)
1765     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1766 
1767     if (zserver_info) {
1768         HashTable *ht = Z_ARRVAL_P(zserver_info);
1769         zval *ztmp;
1770 
1771         if (php_swoole_array_get_value(ht, "host", ztmp)) {
1772             mc->host = std::string(zend::String(ztmp).val());
1773         } else {
1774             zend_throw_exception(swoole_mysql_coro_exception_ce, "Parameter [host] is required", EINVAL);
1775             RETURN_FALSE;
1776         }
1777         if (php_swoole_array_get_value(ht, "port", ztmp)) {
1778             mc->port = zval_get_long(ztmp);
1779         }
1780         if (php_swoole_array_get_value(ht, "ssl", ztmp)) {
1781             mc->ssl = zval_is_true(ztmp);
1782 #ifndef SW_USE_OPENSSL
1783             if (sw_unlikely(mc->ssl)) {
1784                 zend_throw_exception_ex(
1785                     swoole_mysql_coro_exception_ce,
1786                     EPROTONOSUPPORT,
1787                     "you must configure with `--enable-openssl` to support ssl connection when compiling Swoole");
1788                 RETURN_FALSE;
1789             }
1790 #endif
1791         }
1792         if (php_swoole_array_get_value(ht, "user", ztmp)) {
1793             mc->user = std::string(zend::String(ztmp).val());
1794         } else {
1795             zend_throw_exception(swoole_mysql_coro_exception_ce, "Parameter [user] is required", EINVAL);
1796             RETURN_FALSE;
1797         }
1798         if (php_swoole_array_get_value(ht, "password", ztmp)) {
1799             mc->password = std::string(zend::String(ztmp).val());
1800         } else {
1801             zend_throw_exception(swoole_mysql_coro_exception_ce, "Parameter [password] is required", EINVAL);
1802             RETURN_FALSE;
1803         }
1804         if (php_swoole_array_get_value(ht, "database", ztmp)) {
1805             mc->database = std::string(zend::String(ztmp).val());
1806         } else {
1807             zend_throw_exception(swoole_mysql_coro_exception_ce, "Parameter [database] is required", EINVAL);
1808             RETURN_FALSE;
1809         }
1810         if (php_swoole_array_get_value(ht, "timeout", ztmp)) {
1811             mc->connect_timeout = zval_get_double(ztmp);
1812         }
1813         if (php_swoole_array_get_value(ht, "charset", ztmp)) {
1814             zend::String zstr_charset(ztmp);
1815             char charset = mysql::get_charset(zstr_charset.val());
1816             if (UNEXPECTED(charset < 0)) {
1817                 zend_throw_exception_ex(
1818                     swoole_mysql_coro_exception_ce, EINVAL, "Unknown charset [%s]", zstr_charset.val());
1819                 RETURN_FALSE;
1820             }
1821             mc->charset = charset;
1822         }
1823         if (php_swoole_array_get_value(ht, "strict_type", ztmp)) {
1824             mc->strict_type = zval_is_true(ztmp);
1825         }
1826         if (php_swoole_array_get_value(ht, "fetch_mode", ztmp)) {
1827             if (UNEXPECTED(!mc->set_fetch_mode(zval_is_true(ztmp)))) {
1828                 zend_throw_exception_ex(
1829                     swoole_mysql_coro_exception_ce, mc->get_error_code(), "%s", mc->get_error_msg());
1830                 RETURN_FALSE;
1831             }
1832         }
1833     }
1834     if (!mc->connect()) {
1835         zend_update_property_long(
1836             swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connect_errno"), mc->get_error_code());
1837         zend_update_property_string(
1838             swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connect_error"), mc->get_error_msg());
1839         RETURN_FALSE;
1840     }
1841     if (zserver_info && php_swoole_array_length(zserver_info) > 0) {
1842         php_array_merge(Z_ARRVAL_P(sw_zend_read_and_convert_property_array(
1843                             swoole_mysql_coro_ce, ZEND_THIS, ZEND_STRL("serverInfo"), 0)),
1844                         Z_ARRVAL_P(zserver_info));
1845     }
1846     zend_update_property_long(swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("sock"), mc->get_fd());
1847     zend_update_property_bool(swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connected"), 1);
1848     RETURN_TRUE;
1849 }
1850 
PHP_METHOD(swoole_mysql_coro,getDefer)1851 static PHP_METHOD(swoole_mysql_coro, getDefer) {
1852     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
1853     RETURN_BOOL(mc->get_defer());
1854 }
1855 
PHP_METHOD(swoole_mysql_coro,setDefer)1856 static PHP_METHOD(swoole_mysql_coro, setDefer) {
1857     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
1858     zend_bool defer = 1;
1859 
1860     ZEND_PARSE_PARAMETERS_START(0, 1)
1861     Z_PARAM_OPTIONAL
1862     Z_PARAM_BOOL(defer)
1863     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1864 
1865     bool ret = mc->set_defer(defer);
1866     if (UNEXPECTED(!ret)) {
1867         zend_throw_exception_ex(swoole_mysql_coro_exception_ce, mc->get_error_code(), "%s", mc->get_error_msg());
1868     }
1869     RETURN_BOOL(ret);
1870 }
1871 
PHP_METHOD(swoole_mysql_coro,query)1872 static PHP_METHOD(swoole_mysql_coro, query) {
1873     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
1874     char *sql;
1875     size_t sql_length;
1876     double timeout = 0;
1877 
1878     ZEND_PARSE_PARAMETERS_START(1, 2)
1879     Z_PARAM_STRING(sql, sql_length)
1880     Z_PARAM_OPTIONAL
1881     Z_PARAM_DOUBLE(timeout)
1882     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1883 
1884     mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
1885     mc->query(return_value, sql, sql_length);
1886     mc->del_timeout_controller();
1887     swoole_mysql_coro_sync_query_result_properties(ZEND_THIS, mc, return_value);
1888 }
1889 
PHP_METHOD(swoole_mysql_coro,fetch)1890 static PHP_METHOD(swoole_mysql_coro, fetch) {
1891     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
1892     double timeout = 0;
1893 
1894     ZEND_PARSE_PARAMETERS_START(0, 1)
1895     Z_PARAM_OPTIONAL
1896     Z_PARAM_DOUBLE(timeout)
1897     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1898 
1899     mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
1900     mc->fetch(return_value);
1901     mc->del_timeout_controller();
1902     if (sw_unlikely(Z_TYPE_P(return_value) == IS_FALSE)) {
1903         swoole_mysql_coro_sync_error_properties(
1904             ZEND_THIS, mc->get_error_code(), mc->get_error_msg(), mc->is_connected());
1905     }
1906 }
1907 
PHP_METHOD(swoole_mysql_coro,fetchAll)1908 static PHP_METHOD(swoole_mysql_coro, fetchAll) {
1909     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
1910     double timeout = 0;
1911 
1912     ZEND_PARSE_PARAMETERS_START(0, 1)
1913     Z_PARAM_OPTIONAL
1914     Z_PARAM_DOUBLE(timeout)
1915     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1916 
1917     mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
1918     mc->fetch_all(return_value);
1919     mc->del_timeout_controller();
1920     if (sw_unlikely(Z_TYPE_P(return_value) == IS_FALSE)) {
1921         swoole_mysql_coro_sync_error_properties(
1922             ZEND_THIS, mc->get_error_code(), mc->get_error_msg(), mc->is_connected());
1923     }
1924 }
1925 
PHP_METHOD(swoole_mysql_coro,nextResult)1926 static PHP_METHOD(swoole_mysql_coro, nextResult) {
1927     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
1928     double timeout = 0;
1929 
1930     ZEND_PARSE_PARAMETERS_START(0, 1)
1931     Z_PARAM_OPTIONAL
1932     Z_PARAM_DOUBLE(timeout)
1933     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1934 
1935     mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
1936     mc->next_result(return_value);
1937     mc->del_timeout_controller();
1938     swoole_mysql_coro_sync_query_result_properties(ZEND_THIS, mc, return_value);
1939     if (Z_TYPE_P(return_value) == IS_TRUE) {
1940         if (mc->state == SW_MYSQL_STATE_IDLE) {
1941             // the end of procedure
1942             Z_TYPE_INFO_P(return_value) = mc->get_fetch_mode() ? IS_FALSE : IS_NULL;
1943         }
1944     }
1945 }
1946 
PHP_METHOD(swoole_mysql_coro,prepare)1947 static PHP_METHOD(swoole_mysql_coro, prepare) {
1948     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
1949     char *statement;
1950     size_t statement_length;
1951     double timeout = 0;
1952 
1953     ZEND_PARSE_PARAMETERS_START(1, 2)
1954     Z_PARAM_STRING(statement, statement_length)
1955     Z_PARAM_OPTIONAL
1956     Z_PARAM_DOUBLE(timeout)
1957     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1958 
1959     mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
1960     if (UNEXPECTED(!mc->send_prepare_request(statement, statement_length))) {
1961     _failed:
1962         swoole_mysql_coro_sync_error_properties(
1963             ZEND_THIS, mc->get_error_code(), mc->get_error_msg(), mc->is_connected());
1964         RETVAL_FALSE;
1965     } else if (UNEXPECTED(mc->get_defer())) {
1966         RETVAL_TRUE;
1967     } else {
1968         mysql_statement *statement = mc->recv_prepare_response();
1969         if (UNEXPECTED(!statement)) {
1970             goto _failed;
1971         }
1972         RETVAL_OBJ(php_swoole_mysql_coro_statement_create_object(statement, Z_OBJ_P(ZEND_THIS)));
1973     }
1974     mc->del_timeout_controller();
1975 }
1976 
PHP_METHOD(swoole_mysql_coro,recv)1977 static PHP_METHOD(swoole_mysql_coro, recv) {
1978     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
1979     double timeout = 0;
1980 
1981     ZEND_PARSE_PARAMETERS_START(0, 1)
1982     Z_PARAM_OPTIONAL
1983     Z_PARAM_DOUBLE(timeout)
1984     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1985 
1986     if (UNEXPECTED(!mc->check_connection())) {
1987         swoole_mysql_coro_sync_error_properties(ZEND_THIS, mc->get_error_code(), mc->get_error_msg(), false);
1988         RETURN_FALSE;
1989     }
1990     mc->add_timeout_controller(timeout, Socket::TIMEOUT_READ);
1991     switch (mc->state) {
1992     case SW_MYSQL_STATE_IDLE:
1993         swoole_mysql_coro_sync_error_properties(ZEND_THIS, ENOMSG, "no message to receive");
1994         RETVAL_FALSE;
1995         break;
1996     case SW_MYSQL_STATE_QUERY:
1997         mc->recv_query_response(return_value);
1998         break;
1999     case SW_MYSQL_STATE_PREPARE: {
2000         mysql_statement *statement = mc->recv_prepare_response();
2001         if (UNEXPECTED(!statement)) {
2002             RETVAL_FALSE;
2003         } else {
2004             RETVAL_OBJ(php_swoole_mysql_coro_statement_create_object(statement, Z_OBJ_P(ZEND_THIS)));
2005         }
2006         break;
2007     }
2008     default:
2009         if (UNEXPECTED(mc->state & SW_MYSQL_COMMAND_FLAG_EXECUTE)) {
2010             swoole_mysql_coro_sync_error_properties(ZEND_THIS, EPERM, "please use statement to receive data");
2011         } else {
2012             swoole_mysql_coro_sync_error_properties(
2013                 ZEND_THIS, EPERM, "please use fetch/fetchAll/nextResult to get result");
2014         }
2015         RETVAL_FALSE;
2016     }
2017     mc->del_timeout_controller();
2018 }
2019 
swoole_mysql_coro_query_transcation(INTERNAL_FUNCTION_PARAMETERS,const char * command,size_t command_length)2020 static void swoole_mysql_coro_query_transcation(INTERNAL_FUNCTION_PARAMETERS,
2021                                                 const char *command,
2022                                                 size_t command_length) {
2023     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
2024     double timeout = 0;
2025 
2026     ZEND_PARSE_PARAMETERS_START(0, 1)
2027     Z_PARAM_OPTIONAL
2028     Z_PARAM_DOUBLE(timeout)
2029     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
2030 
2031     if (UNEXPECTED(mc->get_defer())) {
2032         zend_throw_exception_ex(
2033             swoole_mysql_coro_exception_ce,
2034             EPERM,
2035             "you should not query transaction when defer mode is on, if you want, please use `query('%s')` instead",
2036             command);
2037         RETURN_FALSE;
2038     }
2039 
2040     mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
2041     mc->query(return_value, command, command_length);
2042     mc->del_timeout_controller();
2043     swoole_mysql_coro_sync_query_result_properties(ZEND_THIS, mc, return_value);
2044 }
2045 
PHP_METHOD(swoole_mysql_coro,begin)2046 static PHP_METHOD(swoole_mysql_coro, begin) {
2047     swoole_mysql_coro_query_transcation(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("BEGIN"));
2048 }
2049 
PHP_METHOD(swoole_mysql_coro,commit)2050 static PHP_METHOD(swoole_mysql_coro, commit) {
2051     swoole_mysql_coro_query_transcation(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("COMMIT"));
2052 }
2053 
PHP_METHOD(swoole_mysql_coro,rollback)2054 static PHP_METHOD(swoole_mysql_coro, rollback) {
2055     swoole_mysql_coro_query_transcation(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ROLLBACK"));
2056 }
2057 
2058 #ifdef SW_USE_MYSQLND
PHP_METHOD(swoole_mysql_coro,escape)2059 static PHP_METHOD(swoole_mysql_coro, escape) {
2060     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
2061     char *str;
2062     size_t str_length;
2063     zend_long flags = 0;
2064 
2065     ZEND_PARSE_PARAMETERS_START(1, 2)
2066     Z_PARAM_STRING(str, str_length)
2067     Z_PARAM_OPTIONAL
2068     Z_PARAM_LONG(flags)
2069     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
2070 
2071     char *newstr = (char *) safe_emalloc(2, str_length + 1, 1);
2072     const MYSQLND_CHARSET *cset = mysqlnd_find_charset_nr(mc->charset);
2073     if (!cset) {
2074         php_swoole_fatal_error(E_ERROR, "unknown mysql charset[%d]", mc->charset);
2075         RETURN_FALSE;
2076     }
2077     zend_ulong newstr_len = mysqlnd_cset_escape_slashes(cset, newstr, str, str_length);
2078     if (newstr_len == (zend_ulong) ~0) {
2079         php_swoole_fatal_error(E_ERROR, "mysqlnd_cset_escape_slashes() failed");
2080         RETURN_FALSE;
2081     }
2082     RETVAL_STRINGL(newstr, newstr_len);
2083     efree(newstr);
2084     return;
2085 }
2086 #endif
2087 
PHP_METHOD(swoole_mysql_coro,close)2088 static PHP_METHOD(swoole_mysql_coro, close) {
2089     mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS);
2090     mc->close();
2091     zend_update_property_bool(swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connected"), 0);
2092     RETURN_TRUE;
2093 }
2094 
PHP_METHOD(swoole_mysql_coro_statement,execute)2095 static PHP_METHOD(swoole_mysql_coro_statement, execute) {
2096     mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS);
2097     zval *params = nullptr;
2098     double timeout = 0;
2099 
2100     ZEND_PARSE_PARAMETERS_START(0, 2)
2101     Z_PARAM_OPTIONAL
2102     Z_PARAM_ARRAY_EX(params, 1, 0)
2103     Z_PARAM_DOUBLE(timeout)
2104     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
2105 
2106     ms->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
2107     ms->execute(return_value, params);
2108     ms->del_timeout_controller();
2109     swoole_mysql_coro_sync_execute_result_properties(ZEND_THIS, return_value);
2110 }
2111 
PHP_METHOD(swoole_mysql_coro_statement,fetch)2112 static PHP_METHOD(swoole_mysql_coro_statement, fetch) {
2113     mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS);
2114     double timeout = 0;
2115 
2116     ZEND_PARSE_PARAMETERS_START(0, 1)
2117     Z_PARAM_OPTIONAL
2118     Z_PARAM_DOUBLE(timeout)
2119     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
2120 
2121     ms->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
2122     ms->fetch(return_value);
2123     ms->del_timeout_controller();
2124     if (sw_unlikely(Z_TYPE_P(return_value) == IS_FALSE)) {
2125         swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, ms->get_error_code(), ms->get_error_msg());
2126     }
2127 }
2128 
PHP_METHOD(swoole_mysql_coro_statement,fetchAll)2129 static PHP_METHOD(swoole_mysql_coro_statement, fetchAll) {
2130     mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS);
2131     double timeout = 0;
2132 
2133     ZEND_PARSE_PARAMETERS_START(0, 1)
2134     Z_PARAM_OPTIONAL
2135     Z_PARAM_DOUBLE(timeout)
2136     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
2137 
2138     ms->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
2139     ms->fetch_all(return_value);
2140     ms->del_timeout_controller();
2141     if (sw_unlikely(Z_TYPE_P(return_value) == IS_FALSE)) {
2142         swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, ms->get_error_code(), ms->get_error_msg());
2143     }
2144 }
2145 
PHP_METHOD(swoole_mysql_coro_statement,nextResult)2146 static PHP_METHOD(swoole_mysql_coro_statement, nextResult) {
2147     mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS);
2148     double timeout = 0;
2149 
2150     ZEND_PARSE_PARAMETERS_START(0, 1)
2151     Z_PARAM_OPTIONAL
2152     Z_PARAM_DOUBLE(timeout)
2153     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
2154 
2155     ms->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR);
2156     ms->next_result(return_value);
2157     ms->del_timeout_controller();
2158     swoole_mysql_coro_sync_execute_result_properties(ZEND_THIS, return_value);
2159     if (Z_TYPE_P(return_value) == IS_TRUE) {
2160         mysql_client *mc = ms->get_client();
2161         if (mc->state == SW_MYSQL_STATE_IDLE) {
2162             // the end of procedure
2163             Z_TYPE_INFO_P(return_value) = mc->get_fetch_mode() ? IS_FALSE : IS_NULL;
2164         }
2165     }
2166 }
2167 
PHP_METHOD(swoole_mysql_coro_statement,recv)2168 static PHP_METHOD(swoole_mysql_coro_statement, recv) {
2169     mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS);
2170     double timeout = 0;
2171     enum sw_mysql_state state;
2172 
2173     ZEND_PARSE_PARAMETERS_START(0, 1)
2174     Z_PARAM_OPTIONAL
2175     Z_PARAM_DOUBLE(timeout)
2176     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
2177 
2178     if (UNEXPECTED(!ms->is_available())) {
2179         swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, ms->get_error_code(), ms->get_error_msg(), false);
2180         RETURN_FALSE;
2181     }
2182     ms->add_timeout_controller(timeout, Socket::TIMEOUT_READ);
2183     switch ((state = ms->get_client()->state)) {
2184     case SW_MYSQL_STATE_IDLE:
2185         swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, ENOMSG, "no message to receive");
2186         RETVAL_FALSE;
2187         break;
2188     case SW_MYSQL_STATE_EXECUTE:
2189         ms->recv_execute_response(return_value);
2190         break;
2191     default:
2192         if (UNEXPECTED(state & SW_MYSQL_COMMAND_FLAG_QUERY)) {
2193             swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, EPERM, "please use client to receive data");
2194         } else {
2195             swoole_mysql_coro_sync_execute_error_properties(
2196                 ZEND_THIS, EPERM, "please use fetch/fetchAll/nextResult to get result");
2197         }
2198         RETVAL_FALSE;
2199     }
2200     ms->del_timeout_controller();
2201 }
2202 
PHP_METHOD(swoole_mysql_coro_statement,close)2203 static PHP_METHOD(swoole_mysql_coro_statement, close) {
2204     mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS);
2205     ms->close();
2206     RETURN_TRUE;
2207 }
2208