1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Andrey Hristov <andrey@php.net> |
16 | Ulf Wendel <uw@php.net> |
17 +----------------------------------------------------------------------+
18 */
19
20 #include "php.h"
21 #include "mysqlnd.h"
22 #include "mysqlnd_wireprotocol.h"
23 #include "mysqlnd_connection.h"
24 #include "mysqlnd_priv.h"
25 #include "mysqlnd_ps.h"
26 #include "mysqlnd_result.h"
27 #include "mysqlnd_result_meta.h"
28 #include "mysqlnd_statistics.h"
29 #include "mysqlnd_debug.h"
30 #include "mysqlnd_block_alloc.h"
31 #include "mysqlnd_ext_plugin.h"
32
33 const char * const mysqlnd_not_bound_as_blob = "Can't send long data for non-string/non-binary data types";
34 const char * const mysqlnd_stmt_not_prepared = "Statement not prepared";
35
36 /* Exported by mysqlnd_ps_codec.c */
37 enum_func_status mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer);
38 enum_func_status mysqlnd_stmt_execute_batch_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer);
39
40 static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt);
41 static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, const unsigned int param_no);
42
mysqlnd_stmt_send_cursor_fetch_command(const MYSQLND_STMT_DATA * stmt,unsigned max_rows)43 static enum_func_status mysqlnd_stmt_send_cursor_fetch_command(
44 const MYSQLND_STMT_DATA *stmt, unsigned max_rows)
45 {
46 MYSQLND_CONN_DATA *conn = stmt->conn;
47 zend_uchar buf[MYSQLND_STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */];
48 const MYSQLND_CSTRING payload = {(const char*) buf, sizeof(buf)};
49
50 int4store(buf, stmt->stmt_id);
51 int4store(buf + MYSQLND_STMT_ID_LENGTH, max_rows);
52
53 if (conn->command->stmt_fetch(conn, payload) == FAIL) {
54 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
55 return FAIL;
56 }
57 return PASS;
58 }
59
mysqlnd_stmt_check_state(const MYSQLND_STMT_DATA * stmt)60 static zend_bool mysqlnd_stmt_check_state(const MYSQLND_STMT_DATA *stmt)
61 {
62 const MYSQLND_CONN_DATA *conn = stmt->conn;
63 if (stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) {
64 return 0;
65 }
66 if (stmt->cursor_exists) {
67 return GET_CONNECTION_STATE(&conn->state) == CONN_READY;
68 } else {
69 return GET_CONNECTION_STATE(&conn->state) == CONN_FETCHING_DATA;
70 }
71 }
72
73 /* {{{ mysqlnd_stmt::store_result */
74 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,store_result)75 MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s)
76 {
77 enum_func_status ret;
78 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
79 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
80 MYSQLND_RES * result;
81
82 DBG_ENTER("mysqlnd_stmt::store_result");
83 if (!stmt || !conn || !stmt->result) {
84 DBG_RETURN(NULL);
85 }
86 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
87
88 /* be compliant with libmysql - NULL will turn */
89 if (!stmt->field_count) {
90 DBG_RETURN(NULL);
91 }
92
93 /* Nothing to store for UPSERT/LOAD DATA*/
94 if (!mysqlnd_stmt_check_state(stmt)) {
95 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
96 DBG_RETURN(NULL);
97 }
98
99 stmt->default_rset_handler = s->m->store_result;
100
101 SET_EMPTY_ERROR(stmt->error_info);
102 SET_EMPTY_ERROR(conn->error_info);
103 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_PS_BUFFERED_SETS);
104
105 if (stmt->cursor_exists) {
106 if (mysqlnd_stmt_send_cursor_fetch_command(stmt, -1) == FAIL) {
107 DBG_RETURN(NULL);
108 }
109 }
110
111 result = stmt->result;
112 result->type = MYSQLND_RES_PS_BUF;
113 /* result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; */
114
115 result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_zval_init(result, result->field_count, TRUE);
116 if (!result->stored_data) {
117 SET_OOM_ERROR(conn->error_info);
118 DBG_RETURN(NULL);
119 }
120
121 ret = result->m.store_result_fetch_data(conn, result, result->meta, &result->stored_data->row_buffers, TRUE);
122
123 result->stored_data->m.fetch_row = mysqlnd_stmt_fetch_row_buffered;
124
125 if (PASS == ret) {
126 if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) {
127 MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
128 if (result->stored_data->row_count) {
129 /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
130 if (result->stored_data->row_count * result->meta->field_count * sizeof(zval *) > SIZE_MAX) {
131 SET_OOM_ERROR(conn->error_info);
132 DBG_RETURN(NULL);
133 }
134 /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */
135 set->data = mnd_emalloc((size_t)(result->stored_data->row_count * result->meta->field_count * sizeof(zval)));
136 if (!set->data) {
137 SET_OOM_ERROR(conn->error_info);
138 DBG_RETURN(NULL);
139 }
140 memset(set->data, 0, (size_t)(result->stored_data->row_count * result->meta->field_count * sizeof(zval)));
141 }
142 /* Position at the first row */
143 set->data_cursor = set->data;
144 } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) {
145 /*TODO*/
146 }
147
148 /* libmysql API docs say it should be so for SELECT statements */
149 UPSERT_STATUS_SET_AFFECTED_ROWS(stmt->upsert_status, stmt->result->stored_data->row_count);
150
151 stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
152 } else {
153 COPY_CLIENT_ERROR(conn->error_info, result->stored_data->error_info);
154 COPY_CLIENT_ERROR(stmt->error_info, result->stored_data->error_info);
155 stmt->result->m.free_result_contents(stmt->result);
156 stmt->result = NULL;
157 stmt->state = MYSQLND_STMT_PREPARED;
158 DBG_RETURN(NULL);
159 }
160
161 DBG_RETURN(result);
162 }
163 /* }}} */
164
165
166 /* {{{ mysqlnd_stmt::get_result */
167 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,get_result)168 MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s)
169 {
170 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
171 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
172 MYSQLND_RES * result;
173
174 DBG_ENTER("mysqlnd_stmt::get_result");
175 if (!stmt || !conn || !stmt->result) {
176 DBG_RETURN(NULL);
177 }
178 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
179
180 /* be compliant with libmysql - NULL will turn */
181 if (!stmt->field_count) {
182 DBG_RETURN(NULL);
183 }
184
185 /* Nothing to store for UPSERT/LOAD DATA*/
186 if (!mysqlnd_stmt_check_state(stmt)) {
187 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
188 DBG_RETURN(NULL);
189 }
190
191 SET_EMPTY_ERROR(stmt->error_info);
192 SET_EMPTY_ERROR(conn->error_info);
193 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_BUFFERED_SETS);
194
195 if (stmt->cursor_exists) {
196 if (mysqlnd_stmt_send_cursor_fetch_command(stmt, -1) == FAIL) {
197 DBG_RETURN(NULL);
198 }
199 }
200
201 do {
202 result = conn->m->result_init(stmt->result->field_count);
203 if (!result) {
204 SET_OOM_ERROR(conn->error_info);
205 break;
206 }
207
208 result->meta = stmt->result->meta->m->clone_metadata(result, stmt->result->meta);
209 if (!result->meta) {
210 SET_OOM_ERROR(conn->error_info);
211 break;
212 }
213
214 if (result->m.store_result(result, conn, MYSQLND_STORE_PS | MYSQLND_STORE_NO_COPY)) {
215 UPSERT_STATUS_SET_AFFECTED_ROWS(stmt->upsert_status, result->stored_data->row_count);
216 stmt->state = MYSQLND_STMT_PREPARED;
217 result->type = MYSQLND_RES_PS_BUF;
218 } else {
219 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
220 stmt->state = MYSQLND_STMT_PREPARED;
221 break;
222 }
223 DBG_RETURN(result);
224 } while (0);
225
226 if (result) {
227 result->m.free_result(result, TRUE);
228 }
229 DBG_RETURN(NULL);
230 }
231 /* }}} */
232
233
234 /* {{{ mysqlnd_stmt::more_results */
235 static zend_bool
MYSQLND_METHOD(mysqlnd_stmt,more_results)236 MYSQLND_METHOD(mysqlnd_stmt, more_results)(const MYSQLND_STMT * s)
237 {
238 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
239 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
240 DBG_ENTER("mysqlnd_stmt::more_results");
241 /* (conn->state == CONN_NEXT_RESULT_PENDING) too */
242 DBG_RETURN((stmt && conn && (conn->m->get_server_status(conn) & SERVER_MORE_RESULTS_EXISTS))? TRUE: FALSE);
243 }
244 /* }}} */
245
246
247 /* {{{ mysqlnd_stmt::next_result */
248 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,next_result)249 MYSQLND_METHOD(mysqlnd_stmt, next_result)(MYSQLND_STMT * s)
250 {
251 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
252 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
253
254 DBG_ENTER("mysqlnd_stmt::next_result");
255 if (!stmt || !conn || !stmt->result) {
256 DBG_RETURN(FAIL);
257 }
258 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
259
260 if (GET_CONNECTION_STATE(&conn->state) != CONN_NEXT_RESULT_PENDING || !(UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS)) {
261 DBG_RETURN(FAIL);
262 }
263
264 DBG_INF_FMT("server_status=%u cursor=%u", UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status), UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_STATUS_CURSOR_EXISTS);
265
266 /* Free space for next result */
267 s->m->free_stmt_result(s);
268 {
269 enum_func_status ret = s->m->parse_execute_response(s, MYSQLND_PARSE_EXEC_RESPONSE_IMPLICIT_NEXT_RESULT);
270 DBG_RETURN(ret);
271 }
272 }
273 /* }}} */
274
275
276 /* {{{ mysqlnd_stmt_skip_metadata */
277 static enum_func_status
mysqlnd_stmt_skip_metadata(MYSQLND_STMT * s)278 mysqlnd_stmt_skip_metadata(MYSQLND_STMT * s)
279 {
280 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
281 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
282 /* Follows parameter metadata, we have just to skip it, as libmysql does */
283 unsigned int i = 0;
284 enum_func_status ret = FAIL;
285 MYSQLND_PACKET_RES_FIELD field_packet;
286 MYSQLND_MEMORY_POOL * pool;
287
288 DBG_ENTER("mysqlnd_stmt_skip_metadata");
289 if (!stmt || !conn) {
290 DBG_RETURN(FAIL);
291 }
292 pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size));
293 if (!pool) {
294 DBG_RETURN(FAIL);
295 }
296 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
297
298 conn->payload_decoder_factory->m.init_result_field_packet(&field_packet);
299 field_packet.memory_pool = pool;
300
301 ret = PASS;
302 field_packet.skip_parsing = TRUE;
303 for (;i < stmt->param_count; i++) {
304 if (FAIL == PACKET_READ(conn, &field_packet)) {
305 ret = FAIL;
306 break;
307 }
308 }
309 PACKET_FREE(&field_packet);
310 mysqlnd_mempool_destroy(pool);
311
312 DBG_RETURN(ret);
313 }
314 /* }}} */
315
316
317 /* {{{ mysqlnd_stmt_read_prepare_response */
318 static enum_func_status
mysqlnd_stmt_read_prepare_response(MYSQLND_STMT * s)319 mysqlnd_stmt_read_prepare_response(MYSQLND_STMT * s)
320 {
321 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
322 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
323 MYSQLND_PACKET_PREPARE_RESPONSE prepare_resp;
324 enum_func_status ret = FAIL;
325
326 DBG_ENTER("mysqlnd_stmt_read_prepare_response");
327 if (!stmt || !conn) {
328 DBG_RETURN(FAIL);
329 }
330 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
331
332 conn->payload_decoder_factory->m.init_prepare_response_packet(&prepare_resp);
333
334 if (FAIL == PACKET_READ(conn, &prepare_resp)) {
335 goto done;
336 }
337
338 if (0xFF == prepare_resp.error_code) {
339 COPY_CLIENT_ERROR(stmt->error_info, prepare_resp.error_info);
340 COPY_CLIENT_ERROR(conn->error_info, prepare_resp.error_info);
341 goto done;
342 }
343 ret = PASS;
344 stmt->stmt_id = prepare_resp.stmt_id;
345 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, prepare_resp.warning_count);
346 UPSERT_STATUS_SET_AFFECTED_ROWS(stmt->upsert_status, 0); /* be like libmysql */
347 stmt->field_count = conn->field_count = prepare_resp.field_count;
348 stmt->param_count = prepare_resp.param_count;
349 done:
350 PACKET_FREE(&prepare_resp);
351
352 DBG_RETURN(ret);
353 }
354 /* }}} */
355
356
357 /* {{{ mysqlnd_stmt_prepare_read_eof */
358 static enum_func_status
mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT * s)359 mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT * s)
360 {
361 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
362 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
363 MYSQLND_PACKET_EOF fields_eof;
364 enum_func_status ret = FAIL;
365
366 DBG_ENTER("mysqlnd_stmt_prepare_read_eof");
367 if (!stmt || !conn) {
368 DBG_RETURN(FAIL);
369 }
370 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
371
372 conn->payload_decoder_factory->m.init_eof_packet(&fields_eof);
373 if (FAIL == (ret = PACKET_READ(conn, &fields_eof))) {
374 if (stmt->result) {
375 stmt->result->m.free_result_contents(stmt->result);
376 /* XXX: This will crash, because we will null also the methods.
377 But seems it happens in extreme cases or doesn't. Should be fixed by exporting a function
378 (from mysqlnd_driver.c?) to do the reset.
379 This bad handling is also in mysqlnd_result.c
380 */
381 memset(stmt, 0, sizeof(MYSQLND_STMT_DATA));
382 stmt->state = MYSQLND_STMT_INITTED;
383 }
384 } else {
385 UPSERT_STATUS_SET_SERVER_STATUS(stmt->upsert_status, fields_eof.server_status);
386 UPSERT_STATUS_SET_WARNINGS(stmt->upsert_status, fields_eof.warning_count);
387 stmt->state = MYSQLND_STMT_PREPARED;
388 }
389
390 DBG_RETURN(ret);
391 }
392 /* }}} */
393
394
395 /* {{{ mysqlnd_stmt::prepare */
396 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,prepare)397 MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const query, const size_t query_len)
398 {
399 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
400 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
401 MYSQLND_STMT * s_to_prepare = s;
402 MYSQLND_STMT_DATA * stmt_to_prepare = stmt;
403
404 DBG_ENTER("mysqlnd_stmt::prepare");
405 if (!stmt || !conn) {
406 DBG_RETURN(FAIL);
407 }
408 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
409 DBG_INF_FMT("query=%s", query);
410
411 UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(stmt->upsert_status);
412 UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
413
414 SET_EMPTY_ERROR(stmt->error_info);
415 SET_EMPTY_ERROR(conn->error_info);
416
417 if (stmt->state > MYSQLND_STMT_INITTED) {
418 /* See if we have to clean the wire */
419 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
420 /* Do implicit use_result and then flush the result */
421 stmt->default_rset_handler = s->m->use_result;
422 stmt->default_rset_handler(s);
423 }
424 /* No 'else' here please :) */
425 if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE && stmt->result) {
426 stmt->result->m.skip_result(stmt->result);
427 }
428 /*
429 Create a new test statement, which we will prepare, but if anything
430 fails, we will scrap it.
431 */
432 s_to_prepare = conn->m->stmt_init(conn);
433 if (!s_to_prepare) {
434 goto fail;
435 }
436 stmt_to_prepare = s_to_prepare->data;
437 }
438
439 {
440 enum_func_status ret = FAIL;
441 const MYSQLND_CSTRING query_string = {query, query_len};
442
443 ret = conn->command->stmt_prepare(conn, query_string);
444 if (FAIL == ret) {
445 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
446 goto fail;
447 }
448 }
449
450 if (FAIL == mysqlnd_stmt_read_prepare_response(s_to_prepare)) {
451 goto fail;
452 }
453
454 if (stmt_to_prepare->param_count) {
455 if (FAIL == mysqlnd_stmt_skip_metadata(s_to_prepare) ||
456 FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare))
457 {
458 goto fail;
459 }
460 }
461
462 /*
463 Read metadata only if there is actual result set.
464 Beware that SHOW statements bypass the PS framework and thus they send
465 no metadata at prepare.
466 */
467 if (stmt_to_prepare->field_count) {
468 MYSQLND_RES * result = conn->m->result_init(stmt_to_prepare->field_count);
469 if (!result) {
470 SET_OOM_ERROR(conn->error_info);
471 goto fail;
472 }
473 /* Allocate the result now as it is needed for the reading of metadata */
474 stmt_to_prepare->result = result;
475
476 result->conn = conn->m->get_reference(conn);
477
478 result->type = MYSQLND_RES_PS_BUF;
479
480 if (FAIL == result->m.read_result_metadata(result, conn) ||
481 FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare))
482 {
483 goto fail;
484 }
485 }
486
487 if (stmt_to_prepare != stmt) {
488 /* swap */
489 size_t real_size = sizeof(MYSQLND_STMT) + mysqlnd_plugin_count() * sizeof(void *);
490 char * tmp_swap = mnd_malloc(real_size);
491 memcpy(tmp_swap, s, real_size);
492 memcpy(s, s_to_prepare, real_size);
493 memcpy(s_to_prepare, tmp_swap, real_size);
494 mnd_free(tmp_swap);
495 {
496 MYSQLND_STMT_DATA * tmp_swap_data = stmt_to_prepare;
497 stmt_to_prepare = stmt;
498 stmt = tmp_swap_data;
499 }
500 s_to_prepare->m->dtor(s_to_prepare, TRUE);
501 }
502 stmt->state = MYSQLND_STMT_PREPARED;
503 DBG_INF("PASS");
504 DBG_RETURN(PASS);
505
506 fail:
507 if (stmt_to_prepare != stmt && s_to_prepare) {
508 s_to_prepare->m->dtor(s_to_prepare, TRUE);
509 }
510 stmt->state = MYSQLND_STMT_INITTED;
511
512 DBG_INF("FAIL");
513 DBG_RETURN(FAIL);
514 }
515 /* }}} */
516
517
518 /* {{{ mysqlnd_stmt_execute_parse_response */
519 static enum_func_status
mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s,enum_mysqlnd_parse_exec_response_type type)520 mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s, enum_mysqlnd_parse_exec_response_type type)
521 {
522 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
523 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
524 enum_func_status ret;
525
526 DBG_ENTER("mysqlnd_stmt_execute_parse_response");
527 if (!stmt || !conn) {
528 DBG_RETURN(FAIL);
529 }
530 SET_CONNECTION_STATE(&conn->state, CONN_QUERY_SENT);
531
532 ret = conn->m->query_read_result_set_header(conn, s);
533 if (ret == FAIL) {
534 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
535 UPSERT_STATUS_RESET(stmt->upsert_status);
536 UPSERT_STATUS_SET_AFFECTED_ROWS(stmt->upsert_status, UPSERT_STATUS_GET_AFFECTED_ROWS(conn->upsert_status));
537 if (GET_CONNECTION_STATE(&conn->state) == CONN_QUIT_SENT) {
538 /* close the statement here, the connection has been closed */
539 }
540 stmt->state = MYSQLND_STMT_PREPARED;
541 stmt->send_types_to_server = 1;
542 } else {
543 /*
544 stmt->send_types_to_server has already been set to 0 in
545 mysqlnd_stmt_execute_generate_request / mysqlnd_stmt_execute_store_params
546 In case there is a situation in which binding was done for integer and the
547 value is > LONG_MAX or < LONG_MIN, there is string conversion and we have
548 to resend the types. Next execution will also need to resend the type.
549 */
550 SET_EMPTY_ERROR(stmt->error_info);
551 SET_EMPTY_ERROR(conn->error_info);
552 UPSERT_STATUS_SET_WARNINGS(stmt->upsert_status, UPSERT_STATUS_GET_WARNINGS(conn->upsert_status));
553 UPSERT_STATUS_SET_AFFECTED_ROWS(stmt->upsert_status, UPSERT_STATUS_GET_AFFECTED_ROWS(conn->upsert_status));
554 UPSERT_STATUS_SET_SERVER_STATUS(stmt->upsert_status, UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status));
555 UPSERT_STATUS_SET_LAST_INSERT_ID(stmt->upsert_status, UPSERT_STATUS_GET_LAST_INSERT_ID(conn->upsert_status));
556
557 stmt->state = MYSQLND_STMT_EXECUTED;
558 if (conn->last_query_type == QUERY_UPSERT || conn->last_query_type == QUERY_LOAD_LOCAL) {
559 DBG_INF("PASS");
560 DBG_RETURN(PASS);
561 }
562
563 stmt->result->type = MYSQLND_RES_PS_BUF;
564 if (!stmt->result->conn) {
565 /*
566 For SHOW we don't create (bypasses PS in server)
567 a result set at prepare and thus a connection was missing
568 */
569 stmt->result->conn = conn->m->get_reference(conn);
570 }
571
572 /* Update stmt->field_count as SHOW sets it to 0 at prepare */
573 stmt->field_count = stmt->result->field_count = conn->field_count;
574 if (stmt->result->stored_data) {
575 stmt->result->stored_data->lengths = NULL;
576 } else if (stmt->result->unbuf) {
577 stmt->result->unbuf->lengths = NULL;
578 }
579 if (stmt->field_count) {
580 stmt->state = MYSQLND_STMT_WAITING_USE_OR_STORE;
581 /*
582 We need to set this because the user might not call
583 use_result() or store_result() and we should be able to scrap the
584 data on the line, if he just decides to close the statement.
585 */
586 DBG_INF_FMT("server_status=%u cursor=%u", UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status),
587 UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) & SERVER_STATUS_CURSOR_EXISTS);
588
589 if (stmt->flags & CURSOR_TYPE_READ_ONLY) {
590 if (UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) & SERVER_STATUS_CURSOR_EXISTS) {
591 DBG_INF("cursor exists");
592 stmt->cursor_exists = TRUE;
593 SET_CONNECTION_STATE(&conn->state, CONN_READY);
594 /* Only cursor read */
595 stmt->default_rset_handler = s->m->use_result;
596 DBG_INF("use_result");
597 } else {
598 DBG_INF("asked for cursor but got none");
599 /*
600 We have asked for CURSOR but got no cursor, because the condition
601 above is not fulfilled. Then...
602
603 This is a single-row result set, a result set with no rows, EXPLAIN,
604 SHOW VARIABLES, or some other command which either a) bypasses the
605 cursors framework in the server and writes rows directly to the
606 network or b) is more efficient if all (few) result set rows are
607 precached on client and server's resources are freed.
608 */
609 /* preferred is buffered read */
610 stmt->default_rset_handler = s->m->store_result;
611 DBG_INF("store_result");
612 }
613 } else {
614 DBG_INF("no cursor");
615 /* preferred is unbuffered read */
616 stmt->default_rset_handler = s->m->use_result;
617 DBG_INF("use_result");
618 }
619 }
620 }
621 #ifndef MYSQLND_DONT_SKIP_OUT_PARAMS_RESULTSET
622 if (UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) & SERVER_PS_OUT_PARAMS) {
623 s->m->free_stmt_content(s);
624 DBG_INF("PS OUT Variable RSet, skipping");
625 /* OUT params result set. Skip for now to retain compatibility */
626 ret = mysqlnd_stmt_execute_parse_response(s, MYSQLND_PARSE_EXEC_RESPONSE_IMPLICIT_OUT_VARIABLES);
627 }
628 #endif
629
630 DBG_INF_FMT("server_status=%u cursor=%u", UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status), UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) & SERVER_STATUS_CURSOR_EXISTS);
631
632 if (ret == PASS && conn->last_query_type == QUERY_UPSERT && UPSERT_STATUS_GET_AFFECTED_ROWS(stmt->upsert_status)) {
633 MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_ROWS_AFFECTED_PS, UPSERT_STATUS_GET_AFFECTED_ROWS(stmt->upsert_status));
634 }
635
636 DBG_INF(ret == PASS? "PASS":"FAIL");
637 DBG_RETURN(ret);
638 }
639 /* }}} */
640
641
642 /* {{{ mysqlnd_stmt::execute */
643 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,execute)644 MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const s)
645 {
646 DBG_ENTER("mysqlnd_stmt::execute");
647 if (FAIL == s->m->send_execute(s, MYSQLND_SEND_EXECUTE_IMPLICIT, NULL, NULL) ||
648 FAIL == s->m->parse_execute_response(s, MYSQLND_PARSE_EXEC_RESPONSE_IMPLICIT))
649 {
650 DBG_RETURN(FAIL);
651 }
652 DBG_RETURN(PASS);
653 }
654 /* }}} */
655
656
657 /* {{{ mysqlnd_stmt::send_execute */
658 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,send_execute)659 MYSQLND_METHOD(mysqlnd_stmt, send_execute)(MYSQLND_STMT * const s, const enum_mysqlnd_send_execute_type type, zval * read_cb, zval * err_cb)
660 {
661 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
662 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
663 enum_func_status ret;
664 zend_uchar *request = NULL;
665 size_t request_len;
666 zend_bool free_request;
667
668 DBG_ENTER("mysqlnd_stmt::send_execute");
669 if (!stmt || !conn) {
670 DBG_RETURN(FAIL);
671 }
672 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
673
674 UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(stmt->upsert_status);
675 UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
676
677 if (stmt->result && stmt->state >= MYSQLND_STMT_PREPARED && stmt->field_count) {
678 s->m->flush(s);
679
680 /*
681 Executed, but the user hasn't started to fetch
682 This will clean also the metadata, but after the EXECUTE call we will
683 have it again.
684 */
685 stmt->result->m.free_result_buffers(stmt->result);
686
687 stmt->state = MYSQLND_STMT_PREPARED;
688 } else if (stmt->state < MYSQLND_STMT_PREPARED) {
689 /* Only initted - error */
690 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
691 DBG_INF("FAIL");
692 DBG_RETURN(FAIL);
693 }
694
695 if (stmt->param_count) {
696 unsigned int i, not_bound = 0;
697 if (!stmt->param_bind) {
698 SET_CLIENT_ERROR(stmt->error_info, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, "No data supplied for parameters in prepared statement");
699 DBG_INF("FAIL");
700 DBG_RETURN(FAIL);
701 }
702 for (i = 0; i < stmt->param_count; i++) {
703 if (Z_ISUNDEF(stmt->param_bind[i].zv)) {
704 not_bound++;
705 }
706 }
707 if (not_bound) {
708 char * msg;
709 mnd_sprintf(&msg, 0, "No data supplied for %u parameter%s in prepared statement",
710 not_bound, not_bound>1 ?"s":"");
711 SET_CLIENT_ERROR(stmt->error_info, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, msg);
712 if (msg) {
713 mnd_sprintf_free(msg);
714 }
715 DBG_INF("FAIL");
716 DBG_RETURN(FAIL);
717 }
718 }
719 ret = s->m->generate_execute_request(s, &request, &request_len, &free_request);
720 if (ret == PASS) {
721 const MYSQLND_CSTRING payload = {(const char*) request, request_len};
722
723 ret = conn->command->stmt_execute(conn, payload);
724 } else {
725 SET_CLIENT_ERROR(stmt->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Couldn't generate the request. Possibly OOM.");
726 }
727
728 if (free_request) {
729 mnd_efree(request);
730 }
731
732 if (ret == FAIL) {
733 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
734 DBG_INF("FAIL");
735 DBG_RETURN(FAIL);
736 }
737 stmt->execute_count++;
738
739 DBG_RETURN(PASS);
740 }
741 /* }}} */
742
743
744 /* {{{ mysqlnd_stmt_fetch_row_buffered */
745 enum_func_status
mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result,void * param,const unsigned int flags,zend_bool * fetched_anything)746 mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything)
747 {
748 MYSQLND_STMT * s = (MYSQLND_STMT *) param;
749 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
750 const MYSQLND_RES_METADATA * const meta = result->meta;
751 unsigned int field_count = meta->field_count;
752
753 DBG_ENTER("mysqlnd_stmt_fetch_row_buffered");
754 *fetched_anything = FALSE;
755 DBG_INF_FMT("stmt=%lu", stmt != NULL ? stmt->stmt_id : 0L);
756
757 /* If we haven't read everything */
758 if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) {
759 MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
760 if (set->data_cursor &&
761 (set->data_cursor - set->data) < (result->stored_data->row_count * field_count))
762 {
763 /* The user could have skipped binding - don't crash*/
764 if (stmt->result_bind) {
765 unsigned int i;
766 zval *current_row = set->data_cursor;
767
768 if (Z_ISUNDEF(current_row[0])) {
769 uint64_t row_num = (set->data_cursor - set->data) / field_count;
770 enum_func_status rc = result->stored_data->m.row_decoder(&result->stored_data->row_buffers[row_num],
771 current_row,
772 meta->field_count,
773 meta->fields,
774 result->conn->options->int_and_float_native,
775 result->conn->stats);
776 if (PASS != rc) {
777 DBG_RETURN(FAIL);
778 }
779 result->stored_data->initialized_rows++;
780 if (stmt->update_max_length) {
781 for (i = 0; i < result->field_count; i++) {
782 /*
783 NULL fields are 0 length, 0 is not more than 0
784 String of zero size, definitely can't be the next max_length.
785 Thus for NULL and zero-length we are quite efficient.
786 */
787 if (Z_TYPE(current_row[i]) == IS_STRING) {
788 zend_ulong len = Z_STRLEN(current_row[i]);
789 if (meta->fields[i].max_length < len) {
790 meta->fields[i].max_length = len;
791 }
792 }
793 }
794 }
795 }
796
797 for (i = 0; i < result->field_count; i++) {
798 /* copy the type */
799 zval *resultzv = &stmt->result_bind[i].zv;
800 if (stmt->result_bind[i].bound == TRUE) {
801 DBG_INF_FMT("i=%u type=%u", i, Z_TYPE(current_row[i]));
802 ZEND_TRY_ASSIGN_COPY_EX(resultzv, ¤t_row[i], 0);
803 }
804 }
805 }
806 set->data_cursor += field_count;
807 *fetched_anything = TRUE;
808 /* buffered result sets don't have a connection */
809 MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF);
810 DBG_INF("row fetched");
811 } else {
812 set->data_cursor = NULL;
813 DBG_INF("no more data");
814 }
815 } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) {
816 /*TODO*/
817 }
818 DBG_INF("PASS");
819 DBG_RETURN(PASS);
820 }
821 /* }}} */
822
823
824 /* {{{ mysqlnd_stmt_fetch_row_unbuffered */
825 enum_func_status
mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES * result,void * param,const unsigned int flags,zend_bool * fetched_anything)826 mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything)
827 {
828 enum_func_status ret;
829 MYSQLND_STMT * s = (MYSQLND_STMT *) param;
830 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
831 MYSQLND_PACKET_ROW * row_packet;
832 MYSQLND_CONN_DATA * conn = result->conn;
833 const MYSQLND_RES_METADATA * const meta = result->meta;
834 void *checkpoint;
835
836 DBG_ENTER("mysqlnd_stmt_fetch_row_unbuffered");
837
838 *fetched_anything = FALSE;
839
840 if (result->unbuf->eof_reached) {
841 /* No more rows obviously */
842 DBG_INF("EOF already reached");
843 DBG_RETURN(PASS);
844 }
845 if (GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA) {
846 SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
847 DBG_ERR("command out of sync");
848 DBG_RETURN(FAIL);
849 }
850 if (!(row_packet = result->unbuf->row_packet)) {
851 DBG_RETURN(FAIL);
852 }
853
854 /* Let the row packet fill our buffer and skip additional malloc + memcpy */
855 row_packet->skip_extraction = stmt && stmt->result_bind? FALSE:TRUE;
856
857 checkpoint = result->memory_pool->checkpoint;
858 mysqlnd_mempool_save_state(result->memory_pool);
859
860 /*
861 If we skip rows (stmt == NULL || stmt->result_bind == NULL) we have to
862 result->unbuf->m.free_last_data() before it. The function returns always true.
863 */
864 if (PASS == (ret = PACKET_READ(conn, row_packet)) && !row_packet->eof) {
865 unsigned int i, field_count = result->field_count;
866
867 if (!row_packet->skip_extraction) {
868 result->unbuf->m.free_last_data(result->unbuf, conn->stats);
869
870 result->unbuf->last_row_data = row_packet->fields;
871 result->unbuf->last_row_buffer = row_packet->row_buffer;
872 row_packet->fields = NULL;
873 row_packet->row_buffer.ptr = NULL;
874
875 if (PASS != result->unbuf->m.row_decoder(&result->unbuf->last_row_buffer,
876 result->unbuf->last_row_data,
877 row_packet->field_count,
878 row_packet->fields_metadata,
879 conn->options->int_and_float_native,
880 conn->stats))
881 {
882 mysqlnd_mempool_restore_state(result->memory_pool);
883 result->memory_pool->checkpoint = checkpoint;
884 DBG_RETURN(FAIL);
885 }
886
887 for (i = 0; i < field_count; i++) {
888 zval *resultzv = &stmt->result_bind[i].zv;
889 if (stmt->result_bind[i].bound == TRUE) {
890 zval *data = &result->unbuf->last_row_data[i];
891
892 if (Z_TYPE_P(data) == IS_STRING && (meta->fields[i].max_length < (zend_ulong) Z_STRLEN_P(data))){
893 meta->fields[i].max_length = Z_STRLEN_P(data);
894 }
895
896 ZEND_TRY_ASSIGN_VALUE_EX(resultzv, data, 0);
897 /* copied data, thus also the ownership. Thus null data */
898 ZVAL_NULL(data);
899 }
900 }
901 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_UNBUF);
902 } else {
903 DBG_INF("skipping extraction");
904 /*
905 Data has been allocated and usually result->unbuf->m.free_last_data()
906 frees it but we can't call this function as it will cause problems with
907 the bound variables. Thus we need to do part of what it does or Zend will
908 report leaks.
909 */
910 row_packet->result_set_memory_pool->free_chunk(
911 row_packet->result_set_memory_pool, row_packet->row_buffer.ptr);
912 row_packet->row_buffer.ptr = NULL;
913 }
914
915 result->unbuf->row_count++;
916 *fetched_anything = TRUE;
917 } else if (ret == FAIL) {
918 if (row_packet->error_info.error_no) {
919 COPY_CLIENT_ERROR(conn->error_info, row_packet->error_info);
920 if (stmt) {
921 COPY_CLIENT_ERROR(stmt->error_info, row_packet->error_info);
922 }
923 }
924 if (GET_CONNECTION_STATE(&conn->state) != CONN_QUIT_SENT) {
925 SET_CONNECTION_STATE(&conn->state, CONN_READY);
926 }
927 result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
928 } else if (row_packet->eof) {
929 DBG_INF("EOF");
930 /* Mark the connection as usable again */
931 result->unbuf->eof_reached = TRUE;
932 UPSERT_STATUS_RESET(conn->upsert_status);
933 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
934 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
935
936 /*
937 result->row_packet will be cleaned when
938 destroying the result object
939 */
940 if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
941 SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
942 } else {
943 SET_CONNECTION_STATE(&conn->state, CONN_READY);
944 }
945 }
946
947 mysqlnd_mempool_restore_state(result->memory_pool);
948 result->memory_pool->checkpoint = checkpoint;
949
950 DBG_INF_FMT("ret=%s fetched_anything=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
951 DBG_RETURN(ret);
952 }
953 /* }}} */
954
955
956 /* {{{ mysqlnd_stmt::use_result */
957 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,use_result)958 MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s)
959 {
960 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
961 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
962 MYSQLND_RES * result;
963
964 DBG_ENTER("mysqlnd_stmt::use_result");
965 if (!stmt || !conn || !stmt->result) {
966 DBG_RETURN(NULL);
967 }
968 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
969
970 if (!stmt->field_count || !mysqlnd_stmt_check_state(stmt)) {
971 SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
972 DBG_ERR("command out of sync");
973 DBG_RETURN(NULL);
974 }
975
976 SET_EMPTY_ERROR(stmt->error_info);
977
978 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_PS_UNBUFFERED_SETS);
979 result = stmt->result;
980
981 result->m.use_result(stmt->result, TRUE);
982 result->unbuf->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor:
983 mysqlnd_stmt_fetch_row_unbuffered;
984 stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
985
986 DBG_INF_FMT("%p", result);
987 DBG_RETURN(result);
988 }
989 /* }}} */
990
991
992 /* {{{ mysqlnd_fetch_row_cursor */
993 enum_func_status
mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result,void * param,const unsigned int flags,zend_bool * fetched_anything)994 mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything)
995 {
996 enum_func_status ret;
997 MYSQLND_STMT * s = (MYSQLND_STMT *) param;
998 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
999 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1000 MYSQLND_PACKET_ROW * row_packet;
1001
1002 DBG_ENTER("mysqlnd_fetch_stmt_row_cursor");
1003
1004 if (!stmt || !stmt->conn || !result || !result->conn || !result->unbuf) {
1005 DBG_ERR("no statement");
1006 DBG_RETURN(FAIL);
1007 }
1008 DBG_INF_FMT("stmt=%lu flags=%u", stmt->stmt_id, flags);
1009
1010 if (stmt->state < MYSQLND_STMT_USER_FETCHING) {
1011 /* Only initted - error */
1012 SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1013 DBG_ERR("command out of sync");
1014 DBG_RETURN(FAIL);
1015 }
1016 if (!(row_packet = result->unbuf->row_packet)) {
1017 DBG_RETURN(FAIL);
1018 }
1019
1020 SET_EMPTY_ERROR(stmt->error_info);
1021 SET_EMPTY_ERROR(conn->error_info);
1022
1023 /* for now fetch only one row */
1024 if (mysqlnd_stmt_send_cursor_fetch_command(stmt, 1) == FAIL) {
1025 DBG_RETURN(FAIL);
1026 }
1027
1028 row_packet->skip_extraction = stmt->result_bind? FALSE:TRUE;
1029
1030 UPSERT_STATUS_RESET(stmt->upsert_status);
1031 if (PASS == (ret = PACKET_READ(conn, row_packet)) && !row_packet->eof) {
1032 const MYSQLND_RES_METADATA * const meta = result->meta;
1033 unsigned int i, field_count = result->field_count;
1034
1035 if (!row_packet->skip_extraction) {
1036 result->unbuf->m.free_last_data(result->unbuf, conn->stats);
1037
1038 result->unbuf->last_row_data = row_packet->fields;
1039 result->unbuf->last_row_buffer = row_packet->row_buffer;
1040 row_packet->fields = NULL;
1041 row_packet->row_buffer.ptr = NULL;
1042
1043 if (PASS != result->unbuf->m.row_decoder(&result->unbuf->last_row_buffer,
1044 result->unbuf->last_row_data,
1045 row_packet->field_count,
1046 row_packet->fields_metadata,
1047 conn->options->int_and_float_native,
1048 conn->stats))
1049 {
1050 DBG_RETURN(FAIL);
1051 }
1052
1053 /* If no result bind, do nothing. We consumed the data */
1054 for (i = 0; i < field_count; i++) {
1055 zval *resultzv = &stmt->result_bind[i].zv;
1056 if (stmt->result_bind[i].bound == TRUE) {
1057 zval *data = &result->unbuf->last_row_data[i];
1058
1059 DBG_INF_FMT("i=%u bound_var=%p type=%u refc=%u", i, &stmt->result_bind[i].zv,
1060 Z_TYPE_P(data), Z_REFCOUNTED(stmt->result_bind[i].zv)?
1061 Z_REFCOUNT(stmt->result_bind[i].zv) : 0);
1062
1063 if (Z_TYPE_P(data) == IS_STRING &&
1064 (meta->fields[i].max_length < (zend_ulong) Z_STRLEN_P(data))) {
1065 meta->fields[i].max_length = Z_STRLEN_P(data);
1066 }
1067
1068 ZEND_TRY_ASSIGN_VALUE_EX(resultzv, data, 0);
1069 /* copied data, thus also the ownership. Thus null data */
1070 ZVAL_NULL(data);
1071 }
1072 }
1073 } else {
1074 DBG_INF("skipping extraction");
1075 /*
1076 Data has been allocated and usually result->unbuf->m.free_last_data()
1077 frees it but we can't call this function as it will cause problems with
1078 the bound variables. Thus we need to do part of what it does or Zend will
1079 report leaks.
1080 */
1081 row_packet->result_set_memory_pool->free_chunk(
1082 row_packet->result_set_memory_pool, row_packet->row_buffer.ptr);
1083 row_packet->row_buffer.ptr = NULL;
1084 }
1085 /* We asked for one row, the next one should be EOF, eat it */
1086 ret = PACKET_READ(conn, row_packet);
1087 if (row_packet->row_buffer.ptr) {
1088 row_packet->result_set_memory_pool->free_chunk(
1089 row_packet->result_set_memory_pool, row_packet->row_buffer.ptr);
1090 row_packet->row_buffer.ptr = NULL;
1091 }
1092 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR);
1093
1094 result->unbuf->row_count++;
1095 *fetched_anything = TRUE;
1096 } else {
1097 *fetched_anything = FALSE;
1098 UPSERT_STATUS_SET_WARNINGS(stmt->upsert_status, row_packet->warning_count);
1099 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
1100
1101 UPSERT_STATUS_SET_SERVER_STATUS(stmt->upsert_status, row_packet->server_status);
1102 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
1103
1104 result->unbuf->eof_reached = row_packet->eof;
1105 }
1106 UPSERT_STATUS_SET_WARNINGS(stmt->upsert_status, row_packet->warning_count);
1107 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
1108
1109 UPSERT_STATUS_SET_SERVER_STATUS(stmt->upsert_status, row_packet->server_status);
1110 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
1111
1112 DBG_INF_FMT("ret=%s fetched=%u server_status=%u warnings=%u eof=%u",
1113 ret == PASS? "PASS":"FAIL", *fetched_anything,
1114 row_packet->server_status, row_packet->warning_count,
1115 result->unbuf->eof_reached);
1116 DBG_RETURN(ret);
1117 }
1118 /* }}} */
1119
1120
1121 /* {{{ mysqlnd_stmt::fetch */
1122 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,fetch)1123 MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const s, zend_bool * const fetched_anything)
1124 {
1125 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1126 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1127 enum_func_status ret;
1128 DBG_ENTER("mysqlnd_stmt::fetch");
1129 if (!stmt || !stmt->conn) {
1130 DBG_RETURN(FAIL);
1131 }
1132 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1133
1134 if (!stmt->result || stmt->state < MYSQLND_STMT_WAITING_USE_OR_STORE) {
1135 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1136 DBG_ERR("command out of sync");
1137 DBG_RETURN(FAIL);
1138 } else if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1139 /* Execute only once. We have to free the previous contents of user's bound vars */
1140
1141 stmt->default_rset_handler(s);
1142 }
1143 stmt->state = MYSQLND_STMT_USER_FETCHING;
1144
1145 SET_EMPTY_ERROR(stmt->error_info);
1146 SET_EMPTY_ERROR(conn->error_info);
1147
1148 ret = stmt->result->m.fetch_row(stmt->result, (void*)s, 0, fetched_anything);
1149 DBG_RETURN(ret);
1150 }
1151 /* }}} */
1152
1153
1154 /* {{{ mysqlnd_stmt::reset */
1155 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,reset)1156 MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const s)
1157 {
1158 enum_func_status ret = PASS;
1159 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1160 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1161
1162 DBG_ENTER("mysqlnd_stmt::reset");
1163 if (!stmt || !conn) {
1164 DBG_RETURN(FAIL);
1165 }
1166 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1167
1168 SET_EMPTY_ERROR(stmt->error_info);
1169 SET_EMPTY_ERROR(conn->error_info);
1170
1171 if (stmt->stmt_id) {
1172 MYSQLND_CONN_DATA * conn = stmt->conn;
1173 if (stmt->param_bind) {
1174 unsigned int i;
1175 DBG_INF("resetting long data");
1176 /* Reset Long Data */
1177 for (i = 0; i < stmt->param_count; i++) {
1178 if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
1179 stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1180 }
1181 }
1182 }
1183
1184 s->m->flush(s);
1185
1186 /*
1187 Don't free now, let the result be usable. When the stmt will again be
1188 executed then the result set will be cleaned, the bound variables will
1189 be separated before that.
1190 */
1191
1192 if (GET_CONNECTION_STATE(&conn->state) == CONN_READY) {
1193 size_t stmt_id = stmt->stmt_id;
1194
1195 ret = stmt->conn->command->stmt_reset(stmt->conn, stmt_id);
1196 if (ret == FAIL) {
1197 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
1198 }
1199 }
1200 *stmt->upsert_status = *conn->upsert_status;
1201 }
1202 DBG_INF(ret == PASS? "PASS":"FAIL");
1203 DBG_RETURN(ret);
1204 }
1205 /* }}} */
1206
1207
1208 /* {{{ mysqlnd_stmt::flush */
1209 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,flush)1210 MYSQLND_METHOD(mysqlnd_stmt, flush)(MYSQLND_STMT * const s)
1211 {
1212 enum_func_status ret = PASS;
1213 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1214 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1215
1216 DBG_ENTER("mysqlnd_stmt::flush");
1217 if (!stmt || !conn) {
1218 DBG_RETURN(FAIL);
1219 }
1220 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1221
1222 if (stmt->stmt_id) {
1223 /*
1224 If the user decided to close the statement right after execute()
1225 We have to call the appropriate use_result() or store_result() and
1226 clean.
1227 */
1228 do {
1229 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1230 DBG_INF("fetching result set header");
1231 stmt->default_rset_handler(s);
1232 stmt->state = MYSQLND_STMT_USER_FETCHING;
1233 }
1234
1235 if (stmt->result) {
1236 DBG_INF("skipping result");
1237 stmt->result->m.skip_result(stmt->result);
1238 }
1239 } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
1240 }
1241 DBG_INF(ret == PASS? "PASS":"FAIL");
1242 DBG_RETURN(ret);
1243 }
1244 /* }}} */
1245
1246
1247 /* {{{ mysqlnd_stmt::send_long_data */
1248 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,send_long_data)1249 MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const s, unsigned int param_no,
1250 const char * const data, zend_ulong data_length)
1251 {
1252 enum_func_status ret = FAIL;
1253 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1254 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1255 zend_uchar * cmd_buf;
1256
1257 DBG_ENTER("mysqlnd_stmt::send_long_data");
1258 if (!stmt || !conn) {
1259 DBG_RETURN(FAIL);
1260 }
1261 DBG_INF_FMT("stmt=%lu param_no=%u data_len=%lu", stmt->stmt_id, param_no, data_length);
1262
1263 SET_EMPTY_ERROR(stmt->error_info);
1264 SET_EMPTY_ERROR(conn->error_info);
1265
1266 if (stmt->state < MYSQLND_STMT_PREPARED) {
1267 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1268 DBG_ERR("not prepared");
1269 DBG_RETURN(FAIL);
1270 }
1271 if (!stmt->param_bind) {
1272 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1273 DBG_ERR("command out of sync");
1274 DBG_RETURN(FAIL);
1275 }
1276 if (param_no >= stmt->param_count) {
1277 SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1278 DBG_ERR("invalid param_no");
1279 DBG_RETURN(FAIL);
1280 }
1281 if (stmt->param_bind[param_no].type != MYSQL_TYPE_LONG_BLOB) {
1282 SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_BUFFER_USE, UNKNOWN_SQLSTATE, mysqlnd_not_bound_as_blob);
1283 DBG_ERR("param_no is not of a blob type");
1284 DBG_RETURN(FAIL);
1285 }
1286
1287 if (GET_CONNECTION_STATE(&conn->state) == CONN_READY) {
1288 const size_t packet_len = MYSQLND_STMT_ID_LENGTH + 2 + data_length;
1289 cmd_buf = mnd_emalloc(packet_len);
1290 if (cmd_buf) {
1291 stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED;
1292
1293 int4store(cmd_buf, stmt->stmt_id);
1294 int2store(cmd_buf + MYSQLND_STMT_ID_LENGTH, param_no);
1295 memcpy(cmd_buf + MYSQLND_STMT_ID_LENGTH + 2, data, data_length);
1296
1297 /* COM_STMT_SEND_LONG_DATA doesn't acknowledge with an OK packet */
1298 {
1299 const MYSQLND_CSTRING payload = {(const char *) cmd_buf, packet_len};
1300
1301 ret = conn->command->stmt_send_long_data(conn, payload);
1302 if (ret == FAIL) {
1303 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
1304 }
1305 }
1306
1307 mnd_efree(cmd_buf);
1308 } else {
1309 ret = FAIL;
1310 SET_OOM_ERROR(stmt->error_info);
1311 SET_OOM_ERROR(conn->error_info);
1312 }
1313 /*
1314 Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not
1315 sent response packets. According to documentation the only way to get an error
1316 is to have out-of-memory on the server-side. However, that's not true, as if
1317 max_allowed_packet_size is smaller than the chunk being sent to the server, the
1318 latter will complain with an error message. However, normally we don't expect
1319 an error message, thus we continue. When sending the next command, which expects
1320 response we will read the unexpected data and error message will look weird.
1321 Therefore we do non-blocking read to clean the line, if there is a need.
1322 Nevertheless, there is a built-in protection when sending a command packet, that
1323 checks if the line is clear - useful for debug purposes and to be switched off
1324 in release builds.
1325
1326 Maybe we can make it automatic by checking what's the value of
1327 max_allowed_packet_size on the server and resending the data.
1328 */
1329 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
1330 #if HAVE_USLEEP && !defined(PHP_WIN32)
1331 usleep(120000);
1332 #endif
1333 if ((packet_len = conn->protocol_frame_codec->m.consume_uneaten_data(conn->protocol_frame_codec, COM_STMT_SEND_LONG_DATA))) {
1334 php_error_docref(NULL, E_WARNING, "There was an error "
1335 "while sending long data. Probably max_allowed_packet_size "
1336 "is smaller than the data. You have to increase it or send "
1337 "smaller chunks of data. Answer was "MYSQLND_SZ_T_SPEC" bytes long.", packet_len);
1338 SET_CLIENT_ERROR(stmt->error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE,
1339 "Server responded to COM_STMT_SEND_LONG_DATA.");
1340 ret = FAIL;
1341 }
1342 #endif
1343 }
1344
1345 DBG_INF(ret == PASS? "PASS":"FAIL");
1346 DBG_RETURN(ret);
1347 }
1348 /* }}} */
1349
1350
1351 /* {{{ mysqlnd_stmt::bind_parameters */
1352 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_parameters)1353 MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * const param_bind)
1354 {
1355 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1356 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1357
1358 DBG_ENTER("mysqlnd_stmt::bind_param");
1359 if (!stmt || !conn) {
1360 DBG_RETURN(FAIL);
1361 }
1362 DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
1363
1364 if (stmt->state < MYSQLND_STMT_PREPARED) {
1365 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1366 DBG_ERR("not prepared");
1367 if (param_bind) {
1368 s->m->free_parameter_bind(s, param_bind);
1369 }
1370 DBG_RETURN(FAIL);
1371 }
1372
1373 SET_EMPTY_ERROR(stmt->error_info);
1374 SET_EMPTY_ERROR(conn->error_info);
1375
1376 if (stmt->param_count) {
1377 unsigned int i = 0;
1378
1379 if (!param_bind) {
1380 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, "Re-binding (still) not supported");
1381 DBG_ERR("Re-binding (still) not supported");
1382 DBG_RETURN(FAIL);
1383 } else if (stmt->param_bind) {
1384 DBG_INF("Binding");
1385 /*
1386 There is already result bound.
1387 Forbid for now re-binding!!
1388 */
1389 for (i = 0; i < stmt->param_count; i++) {
1390 /*
1391 We may have the last reference, then call zval_ptr_dtor() or we may leak memory.
1392 Switching from bind_one_parameter to bind_parameters may result in zv being NULL
1393 */
1394 zval_ptr_dtor(&stmt->param_bind[i].zv);
1395 }
1396 if (stmt->param_bind != param_bind) {
1397 s->m->free_parameter_bind(s, stmt->param_bind);
1398 }
1399 }
1400
1401 stmt->param_bind = param_bind;
1402 for (i = 0; i < stmt->param_count; i++) {
1403 /* The client will use stmt_send_long_data */
1404 DBG_INF_FMT("%u is of type %u", i, stmt->param_bind[i].type);
1405 /* Prevent from freeing */
1406 /* Don't update is_ref, or we will leak during conversion */
1407 Z_TRY_ADDREF(stmt->param_bind[i].zv);
1408 stmt->param_bind[i].flags = 0;
1409 if (stmt->param_bind[i].type == MYSQL_TYPE_LONG_BLOB) {
1410 stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1411 }
1412 }
1413 stmt->send_types_to_server = 1;
1414 }
1415 DBG_INF("PASS");
1416 DBG_RETURN(PASS);
1417 }
1418 /* }}} */
1419
1420
1421 /* {{{ mysqlnd_stmt::bind_one_parameter */
1422 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_parameter)1423 MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter)(MYSQLND_STMT * const s, unsigned int param_no,
1424 zval * const zv, zend_uchar type)
1425 {
1426 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1427 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1428
1429 DBG_ENTER("mysqlnd_stmt::bind_one_parameter");
1430 if (!stmt || !conn) {
1431 DBG_RETURN(FAIL);
1432 }
1433 DBG_INF_FMT("stmt=%lu param_no=%u param_count=%u type=%u", stmt->stmt_id, param_no, stmt->param_count, type);
1434
1435 if (stmt->state < MYSQLND_STMT_PREPARED) {
1436 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1437 DBG_ERR("not prepared");
1438 DBG_RETURN(FAIL);
1439 }
1440
1441 if (param_no >= stmt->param_count) {
1442 SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1443 DBG_ERR("invalid param_no");
1444 DBG_RETURN(FAIL);
1445 }
1446 SET_EMPTY_ERROR(stmt->error_info);
1447 SET_EMPTY_ERROR(conn->error_info);
1448
1449 if (stmt->param_count) {
1450 if (!stmt->param_bind) {
1451 stmt->param_bind = mnd_ecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND));
1452 if (!stmt->param_bind) {
1453 DBG_RETURN(FAIL);
1454 }
1455 }
1456
1457 /* Prevent from freeing */
1458 /* Don't update is_ref, or we will leak during conversion */
1459 Z_TRY_ADDREF_P(zv);
1460 DBG_INF("Binding");
1461 /* Release what we had, if we had */
1462 zval_ptr_dtor(&stmt->param_bind[param_no].zv);
1463 if (type == MYSQL_TYPE_LONG_BLOB) {
1464 /* The client will use stmt_send_long_data */
1465 stmt->param_bind[param_no].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1466 }
1467 ZVAL_COPY_VALUE(&stmt->param_bind[param_no].zv, zv);
1468 stmt->param_bind[param_no].type = type;
1469
1470 stmt->send_types_to_server = 1;
1471 }
1472 DBG_INF("PASS");
1473 DBG_RETURN(PASS);
1474 }
1475 /* }}} */
1476
1477
1478 /* {{{ mysqlnd_stmt::refresh_bind_param */
1479 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,refresh_bind_param)1480 MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param)(MYSQLND_STMT * const s)
1481 {
1482 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1483 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1484
1485 DBG_ENTER("mysqlnd_stmt::refresh_bind_param");
1486 if (!stmt || !conn) {
1487 DBG_RETURN(FAIL);
1488 }
1489 DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
1490
1491 if (stmt->state < MYSQLND_STMT_PREPARED) {
1492 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1493 DBG_ERR("not prepared");
1494 DBG_RETURN(FAIL);
1495 }
1496
1497 SET_EMPTY_ERROR(stmt->error_info);
1498 SET_EMPTY_ERROR(conn->error_info);
1499
1500 if (stmt->param_count) {
1501 stmt->send_types_to_server = 1;
1502 }
1503 DBG_RETURN(PASS);
1504 }
1505 /* }}} */
1506
1507
1508 /* {{{ mysqlnd_stmt::bind_result */
1509 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_result)1510 MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const s,
1511 MYSQLND_RESULT_BIND * const result_bind)
1512 {
1513 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1514 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1515
1516 DBG_ENTER("mysqlnd_stmt::bind_result");
1517 if (!stmt || !conn) {
1518 DBG_RETURN(FAIL);
1519 }
1520 DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
1521
1522 if (stmt->state < MYSQLND_STMT_PREPARED) {
1523 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1524 if (result_bind) {
1525 s->m->free_result_bind(s, result_bind);
1526 }
1527 DBG_ERR("not prepared");
1528 DBG_RETURN(FAIL);
1529 }
1530
1531 SET_EMPTY_ERROR(stmt->error_info);
1532 SET_EMPTY_ERROR(conn->error_info);
1533
1534 if (stmt->field_count) {
1535 unsigned int i = 0;
1536
1537 if (!result_bind) {
1538 DBG_ERR("no result bind passed");
1539 DBG_RETURN(FAIL);
1540 }
1541
1542 mysqlnd_stmt_separate_result_bind(s);
1543 stmt->result_bind = result_bind;
1544 for (i = 0; i < stmt->field_count; i++) {
1545 /* Prevent from freeing */
1546 Z_TRY_ADDREF(stmt->result_bind[i].zv);
1547
1548 DBG_INF_FMT("ref of %p = %u", &stmt->result_bind[i].zv,
1549 Z_REFCOUNTED(stmt->result_bind[i].zv)? Z_REFCOUNT(stmt->result_bind[i].zv) : 0);
1550 /*
1551 Don't update is_ref !!! it's not our job
1552 Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1553 will fail.
1554 */
1555 stmt->result_bind[i].bound = TRUE;
1556 }
1557 } else if (result_bind) {
1558 s->m->free_result_bind(s, result_bind);
1559 }
1560 DBG_INF("PASS");
1561 DBG_RETURN(PASS);
1562 }
1563 /* }}} */
1564
1565
1566 /* {{{ mysqlnd_stmt::bind_result */
1567 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_result)1568 MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned int param_no)
1569 {
1570 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1571 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1572
1573 DBG_ENTER("mysqlnd_stmt::bind_result");
1574 if (!stmt || !conn) {
1575 DBG_RETURN(FAIL);
1576 }
1577 DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
1578
1579 if (stmt->state < MYSQLND_STMT_PREPARED) {
1580 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1581 DBG_ERR("not prepared");
1582 DBG_RETURN(FAIL);
1583 }
1584
1585 if (param_no >= stmt->field_count) {
1586 SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1587 DBG_ERR("invalid param_no");
1588 DBG_RETURN(FAIL);
1589 }
1590
1591 SET_EMPTY_ERROR(stmt->error_info);
1592 SET_EMPTY_ERROR(conn->error_info);
1593
1594 if (stmt->field_count) {
1595 mysqlnd_stmt_separate_one_result_bind(s, param_no);
1596 /* Guaranteed is that stmt->result_bind is NULL */
1597 if (!stmt->result_bind) {
1598 stmt->result_bind = mnd_ecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND));
1599 } else {
1600 stmt->result_bind = mnd_erealloc(stmt->result_bind, stmt->field_count * sizeof(MYSQLND_RESULT_BIND));
1601 }
1602 if (!stmt->result_bind) {
1603 DBG_RETURN(FAIL);
1604 }
1605 ZVAL_NULL(&stmt->result_bind[param_no].zv);
1606 /*
1607 Don't update is_ref !!! it's not our job
1608 Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1609 will fail.
1610 */
1611 stmt->result_bind[param_no].bound = TRUE;
1612 }
1613 DBG_INF("PASS");
1614 DBG_RETURN(PASS);
1615 }
1616 /* }}} */
1617
1618
1619 /* {{{ mysqlnd_stmt::insert_id */
1620 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,insert_id)1621 MYSQLND_METHOD(mysqlnd_stmt, insert_id)(const MYSQLND_STMT * const s)
1622 {
1623 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1624 return stmt? UPSERT_STATUS_GET_LAST_INSERT_ID(stmt->upsert_status) : 0;
1625 }
1626 /* }}} */
1627
1628
1629 /* {{{ mysqlnd_stmt::affected_rows */
1630 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,affected_rows)1631 MYSQLND_METHOD(mysqlnd_stmt, affected_rows)(const MYSQLND_STMT * const s)
1632 {
1633 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1634 return stmt? UPSERT_STATUS_GET_AFFECTED_ROWS(stmt->upsert_status) : 0;
1635 }
1636 /* }}} */
1637
1638
1639 /* {{{ mysqlnd_stmt::num_rows */
1640 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,num_rows)1641 MYSQLND_METHOD(mysqlnd_stmt, num_rows)(const MYSQLND_STMT * const s)
1642 {
1643 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1644 return stmt && stmt->result? mysqlnd_num_rows(stmt->result):0;
1645 }
1646 /* }}} */
1647
1648
1649 /* {{{ mysqlnd_stmt::warning_count */
1650 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,warning_count)1651 MYSQLND_METHOD(mysqlnd_stmt, warning_count)(const MYSQLND_STMT * const s)
1652 {
1653 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1654 return stmt? UPSERT_STATUS_GET_WARNINGS(stmt->upsert_status) : 0;
1655 }
1656 /* }}} */
1657
1658
1659 /* {{{ mysqlnd_stmt::server_status */
1660 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,server_status)1661 MYSQLND_METHOD(mysqlnd_stmt, server_status)(const MYSQLND_STMT * const s)
1662 {
1663 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1664 return stmt? UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) : 0;
1665 }
1666 /* }}} */
1667
1668
1669 /* {{{ mysqlnd_stmt::field_count */
1670 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,field_count)1671 MYSQLND_METHOD(mysqlnd_stmt, field_count)(const MYSQLND_STMT * const s)
1672 {
1673 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1674 return stmt? stmt->field_count : 0;
1675 }
1676 /* }}} */
1677
1678
1679 /* {{{ mysqlnd_stmt::param_count */
1680 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,param_count)1681 MYSQLND_METHOD(mysqlnd_stmt, param_count)(const MYSQLND_STMT * const s)
1682 {
1683 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1684 return stmt? stmt->param_count : 0;
1685 }
1686 /* }}} */
1687
1688
1689 /* {{{ mysqlnd_stmt::errno */
1690 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,errno)1691 MYSQLND_METHOD(mysqlnd_stmt, errno)(const MYSQLND_STMT * const s)
1692 {
1693 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1694 return stmt? stmt->error_info->error_no : 0;
1695 }
1696 /* }}} */
1697
1698
1699 /* {{{ mysqlnd_stmt::error */
1700 static const char *
MYSQLND_METHOD(mysqlnd_stmt,error)1701 MYSQLND_METHOD(mysqlnd_stmt, error)(const MYSQLND_STMT * const s)
1702 {
1703 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1704 return stmt? stmt->error_info->error : 0;
1705 }
1706 /* }}} */
1707
1708
1709 /* {{{ mysqlnd_stmt::sqlstate */
1710 static const char *
MYSQLND_METHOD(mysqlnd_stmt,sqlstate)1711 MYSQLND_METHOD(mysqlnd_stmt, sqlstate)(const MYSQLND_STMT * const s)
1712 {
1713 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1714 return stmt && stmt->error_info->sqlstate[0] ? stmt->error_info->sqlstate:MYSQLND_SQLSTATE_NULL;
1715 }
1716 /* }}} */
1717
1718
1719 /* {{{ mysqlnd_stmt::data_seek */
1720 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,data_seek)1721 MYSQLND_METHOD(mysqlnd_stmt, data_seek)(const MYSQLND_STMT * const s, uint64_t row)
1722 {
1723 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1724 return stmt && stmt->result? stmt->result->m.seek_data(stmt->result, row) : FAIL;
1725 }
1726 /* }}} */
1727
1728
1729 /* {{{ mysqlnd_stmt::param_metadata */
1730 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,param_metadata)1731 MYSQLND_METHOD(mysqlnd_stmt, param_metadata)(MYSQLND_STMT * const s)
1732 {
1733 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1734 if (!stmt || !stmt->param_count) {
1735 return NULL;
1736 }
1737 return NULL;
1738 }
1739 /* }}} */
1740
1741
1742 /* {{{ mysqlnd_stmt::result_metadata */
1743 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,result_metadata)1744 MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s)
1745 {
1746 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1747 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1748 MYSQLND_RES * result_meta = NULL;
1749
1750 DBG_ENTER("mysqlnd_stmt::result_metadata");
1751 if (!stmt || ! conn) {
1752 DBG_RETURN(NULL);
1753 }
1754 DBG_INF_FMT("stmt=%u field_count=%u", stmt->stmt_id, stmt->field_count);
1755
1756 if (!stmt->field_count || !stmt->result || !stmt->result->meta) {
1757 DBG_INF("NULL");
1758 DBG_RETURN(NULL);
1759 }
1760
1761 if (stmt->update_max_length && stmt->result->stored_data) {
1762 /* stored result, we have to update the max_length before we clone the meta data :( */
1763 stmt->result->stored_data->m.initialize_result_set_rest(stmt->result->stored_data,
1764 stmt->result->meta,
1765 conn->stats,
1766 conn->options->int_and_float_native);
1767 }
1768 /*
1769 TODO: This implementation is kind of a hack,
1770 find a better way to do it. In different functions I have put
1771 fuses to check for result->m.fetch_row() being NULL. This should
1772 be handled in a better way.
1773 */
1774 do {
1775 result_meta = conn->m->result_init(stmt->field_count);
1776 if (!result_meta) {
1777 break;
1778 }
1779 result_meta->type = MYSQLND_RES_NORMAL;
1780 result_meta->unbuf = mysqlnd_result_unbuffered_init(result_meta, stmt->field_count, TRUE);
1781 if (!result_meta->unbuf) {
1782 break;
1783 }
1784 result_meta->unbuf->eof_reached = TRUE;
1785 result_meta->meta = stmt->result->meta->m->clone_metadata(result_meta, stmt->result->meta);
1786 if (!result_meta->meta) {
1787 break;
1788 }
1789
1790 DBG_INF_FMT("result_meta=%p", result_meta);
1791 DBG_RETURN(result_meta);
1792 } while (0);
1793
1794 SET_OOM_ERROR(conn->error_info);
1795 if (result_meta) {
1796 result_meta->m.free_result(result_meta, TRUE);
1797 }
1798 DBG_RETURN(NULL);
1799 }
1800 /* }}} */
1801
1802
1803 /* {{{ mysqlnd_stmt::attr_set */
1804 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_set)1805 MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const s,
1806 enum mysqlnd_stmt_attr attr_type,
1807 const void * const value)
1808 {
1809 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1810 DBG_ENTER("mysqlnd_stmt::attr_set");
1811 if (!stmt) {
1812 DBG_RETURN(FAIL);
1813 }
1814 DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type);
1815
1816 switch (attr_type) {
1817 case STMT_ATTR_UPDATE_MAX_LENGTH:{
1818 zend_uchar bval = *(zend_uchar *) value;
1819 /*
1820 XXX : libmysql uses my_bool, but mysqli uses ulong as storage on the stack
1821 and mysqlnd won't be used out of the scope of PHP -> use ulong.
1822 */
1823 stmt->update_max_length = bval? TRUE:FALSE;
1824 break;
1825 }
1826 case STMT_ATTR_CURSOR_TYPE: {
1827 unsigned long ival = *(unsigned long *) value;
1828 if (ival > (unsigned long) CURSOR_TYPE_READ_ONLY) {
1829 SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1830 DBG_INF("FAIL");
1831 DBG_RETURN(FAIL);
1832 }
1833 stmt->flags = ival;
1834 break;
1835 }
1836 case STMT_ATTR_PREFETCH_ROWS: {
1837 unsigned long ival = *(unsigned long *) value;
1838 if (ival == 0) {
1839 ival = MYSQLND_DEFAULT_PREFETCH_ROWS;
1840 } else if (ival > 1) {
1841 SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1842 DBG_INF("FAIL");
1843 DBG_RETURN(FAIL);
1844 }
1845 stmt->prefetch_rows = ival;
1846 break;
1847 }
1848 default:
1849 SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1850 DBG_RETURN(FAIL);
1851 }
1852 DBG_INF("PASS");
1853 DBG_RETURN(PASS);
1854 }
1855 /* }}} */
1856
1857
1858 /* {{{ mysqlnd_stmt::attr_get */
1859 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_get)1860 MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s,
1861 enum mysqlnd_stmt_attr attr_type,
1862 void * const value)
1863 {
1864 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1865 DBG_ENTER("mysqlnd_stmt::attr_set");
1866 if (!stmt) {
1867 DBG_RETURN(FAIL);
1868 }
1869 DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type);
1870
1871 switch (attr_type) {
1872 case STMT_ATTR_UPDATE_MAX_LENGTH:
1873 *(zend_bool *) value= stmt->update_max_length;
1874 break;
1875 case STMT_ATTR_CURSOR_TYPE:
1876 *(unsigned long *) value= stmt->flags;
1877 break;
1878 case STMT_ATTR_PREFETCH_ROWS:
1879 *(unsigned long *) value= stmt->prefetch_rows;
1880 break;
1881 default:
1882 DBG_RETURN(FAIL);
1883 }
1884 DBG_INF_FMT("value=%lu", value);
1885 DBG_RETURN(PASS);
1886 }
1887 /* }}} */
1888
1889
1890 /* free_result() doesn't actually free stmt->result but only the buffers */
1891 /* {{{ mysqlnd_stmt::free_result */
1892 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,free_result)1893 MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const s)
1894 {
1895 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1896 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1897
1898 DBG_ENTER("mysqlnd_stmt::free_result");
1899 if (!stmt || !conn) {
1900 DBG_RETURN(FAIL);
1901 }
1902 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1903
1904 if (!stmt->result) {
1905 DBG_INF("no result");
1906 DBG_RETURN(PASS);
1907 }
1908
1909 /*
1910 If right after execute() we have to call the appropriate
1911 use_result() or store_result() and clean.
1912 */
1913 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1914 DBG_INF("fetching result set header");
1915 /* Do implicit use_result and then flush the result */
1916 stmt->default_rset_handler = s->m->use_result;
1917 stmt->default_rset_handler(s);
1918 }
1919
1920 if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
1921 DBG_INF("skipping result");
1922 /* Flush if anything is left and unbuffered set */
1923 stmt->result->m.skip_result(stmt->result);
1924 /*
1925 Separate the bound variables, which point to the result set, then
1926 destroy the set.
1927 */
1928 mysqlnd_stmt_separate_result_bind(s);
1929
1930 /* Now we can destroy the result set */
1931 stmt->result->m.free_result_buffers(stmt->result);
1932 }
1933
1934 if (stmt->state > MYSQLND_STMT_PREPARED) {
1935 /* As the buffers have been freed, we should go back to PREPARED */
1936 stmt->state = MYSQLND_STMT_PREPARED;
1937 }
1938
1939 if (GET_CONNECTION_STATE(&conn->state) != CONN_QUIT_SENT) {
1940 SET_CONNECTION_STATE(&conn->state, CONN_READY);
1941 }
1942
1943 DBG_RETURN(PASS);
1944 }
1945 /* }}} */
1946
1947
1948 /* {{{ mysqlnd_stmt_separate_result_bind */
1949 static void
mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s)1950 mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s)
1951 {
1952 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1953 unsigned int i;
1954
1955 DBG_ENTER("mysqlnd_stmt_separate_result_bind");
1956 if (!stmt) {
1957 DBG_VOID_RETURN;
1958 }
1959 DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u", stmt->stmt_id, stmt->result_bind, stmt->field_count);
1960
1961 if (!stmt->result_bind) {
1962 DBG_VOID_RETURN;
1963 }
1964
1965 /*
1966 Because only the bound variables can point to our internal buffers, then
1967 separate or free only them. Free is possible because the user could have
1968 lost reference.
1969 */
1970 for (i = 0; i < stmt->field_count; i++) {
1971 /* Let's try with no cache */
1972 if (stmt->result_bind[i].bound == TRUE) {
1973 DBG_INF_FMT("%u has refcount=%u", i, Z_REFCOUNTED(stmt->result_bind[i].zv)? Z_REFCOUNT(stmt->result_bind[i].zv) : 0);
1974 zval_ptr_dtor(&stmt->result_bind[i].zv);
1975 }
1976 }
1977
1978 s->m->free_result_bind(s, stmt->result_bind);
1979 stmt->result_bind = NULL;
1980
1981 DBG_VOID_RETURN;
1982 }
1983 /* }}} */
1984
1985
1986 /* {{{ mysqlnd_stmt_separate_one_result_bind */
1987 static void
mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s,const unsigned int param_no)1988 mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s, const unsigned int param_no)
1989 {
1990 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1991 DBG_ENTER("mysqlnd_stmt_separate_one_result_bind");
1992 if (!stmt) {
1993 DBG_VOID_RETURN;
1994 }
1995 DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u param_no=%u", stmt->stmt_id, stmt->result_bind, stmt->field_count, param_no);
1996
1997 if (!stmt->result_bind) {
1998 DBG_VOID_RETURN;
1999 }
2000
2001 /*
2002 Because only the bound variables can point to our internal buffers, then
2003 separate or free only them. Free is possible because the user could have
2004 lost reference.
2005 */
2006 /* Let's try with no cache */
2007 if (stmt->result_bind[param_no].bound == TRUE) {
2008 DBG_INF_FMT("%u has refcount=%u", param_no, Z_REFCOUNTED(stmt->result_bind[param_no].zv)? Z_REFCOUNT(stmt->result_bind[param_no].zv) : 0);
2009 zval_ptr_dtor(&stmt->result_bind[param_no].zv);
2010 }
2011
2012 DBG_VOID_RETURN;
2013 }
2014 /* }}} */
2015
2016
2017 /* {{{ mysqlnd_stmt::free_stmt_result */
2018 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_result)2019 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)(MYSQLND_STMT * const s)
2020 {
2021 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
2022 DBG_ENTER("mysqlnd_stmt::free_stmt_result");
2023 if (!stmt) {
2024 DBG_VOID_RETURN;
2025 }
2026
2027 /*
2028 First separate the bound variables, which point to the result set, then
2029 destroy the set.
2030 */
2031 mysqlnd_stmt_separate_result_bind(s);
2032 /* Not every statement has a result set attached */
2033 if (stmt->result) {
2034 stmt->result->m.free_result_internal(stmt->result);
2035 stmt->result = NULL;
2036 }
2037 zend_llist_clean(&stmt->error_info->error_list);
2038
2039 DBG_VOID_RETURN;
2040 }
2041 /* }}} */
2042
2043
2044 /* {{{ mysqlnd_stmt::free_stmt_content */
2045 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_content)2046 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content)(MYSQLND_STMT * const s)
2047 {
2048 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
2049 DBG_ENTER("mysqlnd_stmt::free_stmt_content");
2050 if (!stmt) {
2051 DBG_VOID_RETURN;
2052 }
2053 DBG_INF_FMT("stmt=%lu param_bind=%p param_count=%u", stmt->stmt_id, stmt->param_bind, stmt->param_count);
2054
2055 /* Destroy the input bind */
2056 if (stmt->param_bind) {
2057 unsigned int i;
2058 /*
2059 Because only the bound variables can point to our internal buffers, then
2060 separate or free only them. Free is possible because the user could have
2061 lost reference.
2062 */
2063 for (i = 0; i < stmt->param_count; i++) {
2064 /*
2065 If bind_one_parameter was used, but not everything was
2066 bound and nothing was fetched, then some `zv` could be NULL
2067 */
2068 zval_ptr_dtor(&stmt->param_bind[i].zv);
2069 }
2070 s->m->free_parameter_bind(s, stmt->param_bind);
2071 stmt->param_bind = NULL;
2072 }
2073
2074 s->m->free_stmt_result(s);
2075 DBG_VOID_RETURN;
2076 }
2077 /* }}} */
2078
2079
2080 /* {{{ mysqlnd_stmt::close_on_server */
2081 static enum_func_status
MYSQLND_METHOD_PRIVATE(mysqlnd_stmt,close_on_server)2082 MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, close_on_server)(MYSQLND_STMT * const s, zend_bool implicit)
2083 {
2084 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
2085 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
2086 enum_mysqlnd_collected_stats statistic = STAT_LAST;
2087
2088 DBG_ENTER("mysqlnd_stmt::close_on_server");
2089 if (!stmt || !conn) {
2090 DBG_RETURN(FAIL);
2091 }
2092 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
2093
2094 SET_EMPTY_ERROR(stmt->error_info);
2095 SET_EMPTY_ERROR(conn->error_info);
2096
2097 /*
2098 If the user decided to close the statement right after execute()
2099 We have to call the appropriate use_result() or store_result() and
2100 clean.
2101 */
2102 do {
2103 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
2104 DBG_INF("fetching result set header");
2105 stmt->default_rset_handler(s);
2106 stmt->state = MYSQLND_STMT_USER_FETCHING;
2107 }
2108
2109 /* unbuffered set not fetched to the end ? Clean the line */
2110 if (stmt->result) {
2111 DBG_INF("skipping result");
2112 stmt->result->m.skip_result(stmt->result);
2113 }
2114 } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
2115 /*
2116 After this point we are allowed to free the result set,
2117 as we have cleaned the line
2118 */
2119 if (stmt->stmt_id) {
2120 MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_FREE_RESULT_IMPLICIT:
2121 STAT_FREE_RESULT_EXPLICIT);
2122
2123 if (GET_CONNECTION_STATE(&conn->state) == CONN_READY) {
2124 enum_func_status ret = FAIL;
2125 const size_t stmt_id = stmt->stmt_id;
2126
2127 ret = conn->command->stmt_close(conn, stmt_id);
2128 if (ret == FAIL) {
2129 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
2130 DBG_RETURN(FAIL);
2131 }
2132 }
2133 }
2134 switch (stmt->execute_count) {
2135 case 0:
2136 statistic = STAT_PS_PREPARED_NEVER_EXECUTED;
2137 break;
2138 case 1:
2139 statistic = STAT_PS_PREPARED_ONCE_USED;
2140 break;
2141 default:
2142 break;
2143 }
2144 if (statistic != STAT_LAST) {
2145 MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
2146 }
2147
2148 if (stmt->execute_cmd_buffer.buffer) {
2149 mnd_efree(stmt->execute_cmd_buffer.buffer);
2150 stmt->execute_cmd_buffer.buffer = NULL;
2151 }
2152
2153 s->m->free_stmt_content(s);
2154
2155 if (conn) {
2156 conn->m->free_reference(conn);
2157 stmt->conn = NULL;
2158 }
2159
2160 DBG_RETURN(PASS);
2161 }
2162 /* }}} */
2163
2164 /* {{{ mysqlnd_stmt::dtor */
2165 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,dtor)2166 MYSQLND_METHOD(mysqlnd_stmt, dtor)(MYSQLND_STMT * const s, zend_bool implicit)
2167 {
2168 MYSQLND_STMT_DATA * stmt = (s != NULL) ? s->data:NULL;
2169 enum_func_status ret = FAIL;
2170
2171 DBG_ENTER("mysqlnd_stmt::dtor");
2172 if (stmt) {
2173 DBG_INF_FMT("stmt=%p", stmt);
2174
2175 MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_STMT_CLOSE_IMPLICIT:
2176 STAT_STMT_CLOSE_EXPLICIT);
2177
2178 ret = s->m->close_on_server(s, implicit);
2179 mnd_efree(stmt);
2180 }
2181 mnd_efree(s);
2182
2183 DBG_INF(ret == PASS? "PASS":"FAIL");
2184 DBG_RETURN(ret);
2185 }
2186 /* }}} */
2187
2188
2189 /* {{{ mysqlnd_stmt::alloc_param_bind */
2190 static MYSQLND_PARAM_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_param_bind)2191 MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind)(MYSQLND_STMT * const s)
2192 {
2193 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
2194 DBG_ENTER("mysqlnd_stmt::alloc_param_bind");
2195 if (!stmt) {
2196 DBG_RETURN(NULL);
2197 }
2198 DBG_RETURN(mnd_ecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND)));
2199 }
2200 /* }}} */
2201
2202
2203 /* {{{ mysqlnd_stmt::alloc_result_bind */
2204 static MYSQLND_RESULT_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_result_bind)2205 MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind)(MYSQLND_STMT * const s)
2206 {
2207 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
2208 DBG_ENTER("mysqlnd_stmt::alloc_result_bind");
2209 if (!stmt) {
2210 DBG_RETURN(NULL);
2211 }
2212 DBG_RETURN(mnd_ecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND)));
2213 }
2214 /* }}} */
2215
2216
2217 /* {{{ param_bind::free_parameter_bind */
2218 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_parameter_bind)2219 MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * param_bind)
2220 {
2221 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
2222 if (stmt) {
2223 mnd_efree(param_bind);
2224 }
2225 }
2226 /* }}} */
2227
2228
2229 /* {{{ mysqlnd_stmt::free_result_bind */
2230 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_result_bind)2231 MYSQLND_METHOD(mysqlnd_stmt, free_result_bind)(MYSQLND_STMT * const s, MYSQLND_RESULT_BIND * result_bind)
2232 {
2233 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
2234 if (stmt) {
2235 mnd_efree(result_bind);
2236 }
2237 }
2238 /* }}} */
2239
2240
2241
2242 MYSQLND_CLASS_METHODS_START(mysqlnd_stmt)
2243 MYSQLND_METHOD(mysqlnd_stmt, prepare),
2244 MYSQLND_METHOD(mysqlnd_stmt, send_execute),
2245 MYSQLND_METHOD(mysqlnd_stmt, execute),
2246 MYSQLND_METHOD(mysqlnd_stmt, use_result),
2247 MYSQLND_METHOD(mysqlnd_stmt, store_result),
2248 MYSQLND_METHOD(mysqlnd_stmt, get_result),
2249 MYSQLND_METHOD(mysqlnd_stmt, more_results),
2250 MYSQLND_METHOD(mysqlnd_stmt, next_result),
2251 MYSQLND_METHOD(mysqlnd_stmt, free_result),
2252 MYSQLND_METHOD(mysqlnd_stmt, data_seek),
2253 MYSQLND_METHOD(mysqlnd_stmt, reset),
2254 MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, close_on_server),
2255 MYSQLND_METHOD(mysqlnd_stmt, dtor),
2256
2257 MYSQLND_METHOD(mysqlnd_stmt, fetch),
2258
2259 MYSQLND_METHOD(mysqlnd_stmt, bind_parameters),
2260 MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter),
2261 MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param),
2262 MYSQLND_METHOD(mysqlnd_stmt, bind_result),
2263 MYSQLND_METHOD(mysqlnd_stmt, bind_one_result),
2264 MYSQLND_METHOD(mysqlnd_stmt, send_long_data),
2265 MYSQLND_METHOD(mysqlnd_stmt, param_metadata),
2266 MYSQLND_METHOD(mysqlnd_stmt, result_metadata),
2267
2268 MYSQLND_METHOD(mysqlnd_stmt, insert_id),
2269 MYSQLND_METHOD(mysqlnd_stmt, affected_rows),
2270 MYSQLND_METHOD(mysqlnd_stmt, num_rows),
2271
2272 MYSQLND_METHOD(mysqlnd_stmt, param_count),
2273 MYSQLND_METHOD(mysqlnd_stmt, field_count),
2274 MYSQLND_METHOD(mysqlnd_stmt, warning_count),
2275
2276 MYSQLND_METHOD(mysqlnd_stmt, errno),
2277 MYSQLND_METHOD(mysqlnd_stmt, error),
2278 MYSQLND_METHOD(mysqlnd_stmt, sqlstate),
2279
2280 MYSQLND_METHOD(mysqlnd_stmt, attr_get),
2281 MYSQLND_METHOD(mysqlnd_stmt, attr_set),
2282
2283
2284 MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind),
2285 MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind),
2286 MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind),
2287 MYSQLND_METHOD(mysqlnd_stmt, free_result_bind),
2288 MYSQLND_METHOD(mysqlnd_stmt, server_status),
2289 mysqlnd_stmt_execute_generate_request,
2290 mysqlnd_stmt_execute_parse_response,
2291 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content),
2292 MYSQLND_METHOD(mysqlnd_stmt, flush),
2293 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)
2294 MYSQLND_CLASS_METHODS_END;
2295
2296
2297 /* {{{ _mysqlnd_init_ps_subsystem */
_mysqlnd_init_ps_subsystem()2298 void _mysqlnd_init_ps_subsystem()
2299 {
2300 mysqlnd_stmt_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_stmt));
2301 _mysqlnd_init_ps_fetch_subsystem();
2302 }
2303 /* }}} */
2304