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