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