1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2006-2016 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@mysql.com> |
16 | Ulf Wendel <uwendel@mysql.com> |
17 | Georg Richter <georg@mysql.com> |
18 +----------------------------------------------------------------------+
19 */
20
21 /* $Id$ */
22 #include "php.h"
23 #include "mysqlnd.h"
24 #include "mysqlnd_wireprotocol.h"
25 #include "mysqlnd_priv.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 #define MYSQLND_SILENT
34
35
36 const char * const mysqlnd_not_bound_as_blob = "Can't send long data for non-string/non-binary data types";
37 const char * const mysqlnd_stmt_not_prepared = "Statement not prepared";
38
39 /* Exported by mysqlnd_ps_codec.c */
40 enum_func_status mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC);
41
42 static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC);
43 static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC);
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 TSRMLS_DC)
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 TSRMLS_CC));
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC);
132 mnd_efree(stmt->result);
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 TSRMLS_DC)
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 TSRMLS_CC));
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC))) {
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 TSRMLS_CC);
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 TSRMLS_DC)
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 TSRMLS_CC) & 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 TSRMLS_DC)
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 TSRMLS_CC);
248 {
249 enum_func_status ret = s->m->parse_execute_response(s TSRMLS_CC);
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 TSRMLS_DC)258 mysqlnd_stmt_skip_metadata(MYSQLND_STMT * s TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_DC)295 mysqlnd_stmt_read_prepare_response(MYSQLND_STMT * s TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_DC)339 mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT * s TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_CC);
359 mnd_efree(stmt->result);
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 TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC) ||
420 FAIL == mysqlnd_stmt_read_prepare_response(s_to_prepare TSRMLS_CC))
421 {
422 goto fail;
423 }
424
425 if (stmt_to_prepare->param_count) {
426 if (FAIL == mysqlnd_stmt_skip_metadata(s_to_prepare TSRMLS_CC) ||
427 FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare TSRMLS_CC))
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 TSRMLS_CC);
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 TSRMLS_CC);
448
449 result->type = MYSQLND_RES_PS_BUF;
450
451 if (FAIL == result->m.read_result_metadata(result, stmt_to_prepare->conn TSRMLS_CC) ||
452 FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare TSRMLS_CC))
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_DC)491 mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC);
593 }
594 #endif
595
596 DBG_INF(ret == PASS? "PASS":"FAIL");
597 DBG_RETURN(ret);
598 }
599 /* }}} */
600
601
602 /* {{{ mysqlnd_stmt::execute */
603 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,execute)604 MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const s TSRMLS_DC)
605 {
606 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
607 enum_func_status ret;
608 MYSQLND_CONN_DATA * conn;
609 zend_uchar *request = NULL;
610 size_t request_len;
611 zend_bool free_request;
612
613 DBG_ENTER("mysqlnd_stmt::execute");
614 if (!stmt || !stmt->conn) {
615 DBG_RETURN(FAIL);
616 }
617 conn = stmt->conn;
618 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
619
620 SET_ERROR_AFF_ROWS(stmt);
621 SET_ERROR_AFF_ROWS(stmt->conn);
622
623 if (stmt->result && stmt->state >= MYSQLND_STMT_PREPARED && stmt->field_count) {
624 /*
625 We don need to copy the data from the buffers which we will clean.
626 Because it has already been copied. See
627 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
628 */
629 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
630 if (stmt->result_bind &&
631 stmt->result_zvals_separated_once == TRUE &&
632 stmt->state >= MYSQLND_STMT_USER_FETCHING)
633 {
634 /*
635 We need to copy the data from the buffers which we will clean.
636 The bound variables point to them only if the user has started
637 to fetch data (MYSQLND_STMT_USER_FETCHING).
638 We need to check 'result_zvals_separated_once' or we will leak
639 in the following scenario
640 prepare("select 1 from dual");
641 execute();
642 fetch(); <-- no binding, but that's not a problem
643 bind_result();
644 execute(); <-- here we will leak because we separate without need
645 */
646 unsigned int i;
647 for (i = 0; i < stmt->field_count; i++) {
648 if (stmt->result_bind[i].bound == TRUE) {
649 zval_copy_ctor(stmt->result_bind[i].zv);
650 }
651 }
652 }
653 #endif
654
655 s->m->flush(s TSRMLS_CC);
656
657 /*
658 Executed, but the user hasn't started to fetch
659 This will clean also the metadata, but after the EXECUTE call we will
660 have it again.
661 */
662 stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC);
663
664 stmt->state = MYSQLND_STMT_PREPARED;
665 } else if (stmt->state < MYSQLND_STMT_PREPARED) {
666 /* Only initted - error */
667 SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
668 mysqlnd_out_of_sync);
669 SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
670 DBG_INF("FAIL");
671 DBG_RETURN(FAIL);
672 }
673
674 if (stmt->param_count) {
675 unsigned int i, not_bound = 0;
676 if (!stmt->param_bind) {
677 SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE,
678 "No data supplied for parameters in prepared statement");
679 DBG_INF("FAIL");
680 DBG_RETURN(FAIL);
681 }
682 for (i = 0; i < stmt->param_count; i++) {
683 if (stmt->param_bind[i].zv == NULL) {
684 not_bound++;
685 }
686 }
687 if (not_bound) {
688 char * msg;
689 mnd_sprintf(&msg, 0, "No data supplied for %u parameter%s in prepared statement",
690 not_bound, not_bound>1 ?"s":"");
691 SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, msg);
692 if (msg) {
693 mnd_sprintf_free(msg);
694 }
695 DBG_INF("FAIL");
696 DBG_RETURN(FAIL);
697 }
698 }
699 ret = s->m->generate_execute_request(s, &request, &request_len, &free_request TSRMLS_CC);
700 if (ret == PASS) {
701 /* support for buffer types should be added here ! */
702 ret = stmt->conn->m->simple_command(stmt->conn, COM_STMT_EXECUTE, request, request_len,
703 PROT_LAST /* we will handle the response packet*/,
704 FALSE, FALSE TSRMLS_CC);
705 } else {
706 SET_STMT_ERROR(stmt, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Couldn't generate the request. Possibly OOM.");
707 }
708
709 if (free_request) {
710 mnd_efree(request);
711 }
712
713 if (ret == FAIL) {
714 COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info);
715 DBG_INF("FAIL");
716 DBG_RETURN(FAIL);
717 }
718 stmt->execute_count++;
719
720 ret = s->m->parse_execute_response(s TSRMLS_CC);
721
722 DBG_INF_FMT("server_status=%u cursor=%u", stmt->upsert_status->server_status, stmt->upsert_status->server_status & SERVER_STATUS_CURSOR_EXISTS);
723
724 if (ret == PASS && conn->last_query_type == QUERY_UPSERT && stmt->upsert_status->affected_rows) {
725 MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_ROWS_AFFECTED_PS, stmt->upsert_status->affected_rows);
726 }
727 DBG_RETURN(ret);
728 }
729 /* }}} */
730
731
732 /* {{{ mysqlnd_stmt_fetch_row_buffered */
733 enum_func_status
mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)734 mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC)
735 {
736 MYSQLND_STMT * s = (MYSQLND_STMT *) param;
737 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
738 const MYSQLND_RES_METADATA * const meta = result->meta;
739 unsigned int field_count = meta->field_count;
740
741 DBG_ENTER("mysqlnd_stmt_fetch_row_buffered");
742 *fetched_anything = FALSE;
743 DBG_INF_FMT("stmt=%lu", stmt != NULL ? stmt->stmt_id : 0L);
744
745 /* If we haven't read everything */
746 if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) {
747 MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
748 if (set->data_cursor &&
749 (set->data_cursor - set->data) < (result->stored_data->row_count * field_count))
750 {
751 /* The user could have skipped binding - don't crash*/
752 if (stmt->result_bind) {
753 unsigned int i;
754 zval **current_row = set->data_cursor;
755
756 if (NULL == current_row[0]) {
757 uint64_t row_num = (set->data_cursor - set->data) / field_count;
758 enum_func_status rc = result->stored_data->m.row_decoder(result->stored_data->row_buffers[row_num],
759 current_row,
760 meta->field_count,
761 meta->fields,
762 result->conn->options->int_and_float_native,
763 result->conn->stats TSRMLS_CC);
764 if (PASS != rc) {
765 DBG_RETURN(FAIL);
766 }
767 result->stored_data->initialized_rows++;
768 if (stmt->update_max_length) {
769 for (i = 0; i < result->field_count; i++) {
770 /*
771 NULL fields are 0 length, 0 is not more than 0
772 String of zero size, definitely can't be the next max_length.
773 Thus for NULL and zero-length we are quite efficient.
774 */
775 if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
776 unsigned long len = Z_STRLEN_P(current_row[i]);
777 if (meta->fields[i].max_length < len) {
778 meta->fields[i].max_length = len;
779 }
780 }
781 }
782 }
783 }
784
785 for (i = 0; i < result->field_count; i++) {
786 /* Clean what we copied last time */
787 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
788 if (stmt->result_bind[i].zv) {
789 zval_dtor(stmt->result_bind[i].zv);
790 }
791 #endif
792 /* copy the type */
793 if (stmt->result_bind[i].bound == TRUE) {
794 DBG_INF_FMT("i=%u type=%u", i, Z_TYPE_P(current_row[i]));
795 if (Z_TYPE_P(current_row[i]) != IS_NULL) {
796 /*
797 Copy the value.
798 Pre-condition is that the zvals in the result_bind buffer
799 have been ZVAL_NULL()-ed or to another simple type
800 (int, double, bool but not string). Because of the reference
801 counting the user can't delete the strings the variables point to.
802 */
803
804 Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]);
805 stmt->result_bind[i].zv->value = current_row[i]->value;
806 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
807 zval_copy_ctor(stmt->result_bind[i].zv);
808 #endif
809 } else {
810 ZVAL_NULL(stmt->result_bind[i].zv);
811 }
812 }
813 }
814 }
815 set->data_cursor += field_count;
816 *fetched_anything = TRUE;
817 /* buffered result sets don't have a connection */
818 MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF);
819 DBG_INF("row fetched");
820 } else {
821 set->data_cursor = NULL;
822 DBG_INF("no more data");
823 }
824 } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) {
825 /*TODO*/
826 }
827 DBG_INF("PASS");
828 DBG_RETURN(PASS);
829 }
830 /* }}} */
831
832
833 /* {{{ mysqlnd_stmt_fetch_row_unbuffered */
834 enum_func_status
mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)835 mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC)
836 {
837 enum_func_status ret;
838 MYSQLND_STMT * s = (MYSQLND_STMT *) param;
839 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
840 MYSQLND_PACKET_ROW * row_packet;
841 const MYSQLND_RES_METADATA * const meta = result->meta;
842
843 DBG_ENTER("mysqlnd_stmt_fetch_row_unbuffered");
844
845 *fetched_anything = FALSE;
846
847 if (result->unbuf->eof_reached) {
848 /* No more rows obviously */
849 DBG_INF("EOF already reached");
850 DBG_RETURN(PASS);
851 }
852 if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
853 SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
854 UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
855 DBG_ERR("command out of sync");
856 DBG_RETURN(FAIL);
857 }
858 if (!(row_packet = result->unbuf->row_packet)) {
859 DBG_RETURN(FAIL);
860 }
861
862 /* Let the row packet fill our buffer and skip additional malloc + memcpy */
863 row_packet->skip_extraction = stmt && stmt->result_bind? FALSE:TRUE;
864
865 /*
866 If we skip rows (stmt == NULL || stmt->result_bind == NULL) we have to
867 result->unbuf->m.free_last_data() before it. The function returns always true.
868 */
869 if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
870 unsigned int i, field_count = result->field_count;
871
872 if (!row_packet->skip_extraction) {
873 result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC);
874
875 result->unbuf->last_row_data = row_packet->fields;
876 result->unbuf->last_row_buffer = row_packet->row_buffer;
877 row_packet->fields = NULL;
878 row_packet->row_buffer = NULL;
879
880 if (PASS != result->unbuf->m.row_decoder(result->unbuf->last_row_buffer,
881 result->unbuf->last_row_data,
882 row_packet->field_count,
883 row_packet->fields_metadata,
884 result->conn->options->int_and_float_native,
885 result->conn->stats TSRMLS_CC))
886 {
887 DBG_RETURN(FAIL);
888 }
889
890 for (i = 0; i < field_count; i++) {
891 if (stmt->result_bind[i].bound == TRUE) {
892 zval *data = result->unbuf->last_row_data[i];
893 /*
894 stmt->result_bind[i].zv has been already destructed
895 in result->unbuf->m.free_last_data()
896 */
897 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
898 zval_dtor(stmt->result_bind[i].zv);
899 #endif
900 if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) {
901 if ((Z_TYPE_P(data) == IS_STRING) && (meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) {
902 meta->fields[i].max_length = Z_STRLEN_P(data);
903 }
904 stmt->result_bind[i].zv->value = data->value;
905 /* copied data, thus also the ownership. Thus null data */
906 ZVAL_NULL(data);
907 }
908 }
909 }
910 MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_UNBUF);
911 } else {
912 DBG_INF("skipping extraction");
913 /*
914 Data has been allocated and usually result->unbuf->m.free_last_data()
915 frees it but we can't call this function as it will cause problems with
916 the bound variables. Thus we need to do part of what it does or Zend will
917 report leaks.
918 */
919 row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC);
920 row_packet->row_buffer = NULL;
921 }
922
923 result->unbuf->row_count++;
924 *fetched_anything = TRUE;
925 } else if (ret == FAIL) {
926 if (row_packet->error_info.error_no) {
927 COPY_CLIENT_ERROR(*stmt->conn->error_info, row_packet->error_info);
928 COPY_CLIENT_ERROR(*stmt->error_info, row_packet->error_info);
929 }
930 CONN_SET_STATE(result->conn, CONN_READY);
931 result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
932 } else if (row_packet->eof) {
933 DBG_INF("EOF");
934 /* Mark the connection as usable again */
935 result->unbuf->eof_reached = TRUE;
936 memset(result->conn->upsert_status, 0, sizeof(*result->conn->upsert_status));
937 result->conn->upsert_status->warning_count = row_packet->warning_count;
938 result->conn->upsert_status->server_status = row_packet->server_status;
939 /*
940 result->row_packet will be cleaned when
941 destroying the result object
942 */
943 if (result->conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
944 CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
945 } else {
946 CONN_SET_STATE(result->conn, CONN_READY);
947 }
948 }
949
950 DBG_INF_FMT("ret=%s fetched_anything=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
951 DBG_RETURN(ret);
952 }
953 /* }}} */
954
955
956 /* {{{ mysqlnd_stmt::use_result */
957 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,use_result)958 MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s TSRMLS_DC)
959 {
960 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
961 MYSQLND_RES * result;
962 MYSQLND_CONN_DATA * conn;
963
964 DBG_ENTER("mysqlnd_stmt::use_result");
965 if (!stmt || !stmt->conn || !stmt->result) {
966 DBG_RETURN(NULL);
967 }
968 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
969
970 conn = stmt->conn;
971
972 if (!stmt->field_count ||
973 (!stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_FETCHING_DATA) ||
974 (stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_READY) ||
975 (stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE))
976 {
977 SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
978 UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
979 DBG_ERR("command out of sync");
980 DBG_RETURN(NULL);
981 }
982
983 SET_EMPTY_ERROR(*stmt->error_info);
984
985 MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_PS_UNBUFFERED_SETS);
986 result = stmt->result;
987
988 result->m.use_result(stmt->result, TRUE TSRMLS_CC);
989 result->unbuf->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor:
990 mysqlnd_stmt_fetch_row_unbuffered;
991 stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
992
993 DBG_INF_FMT("%p", result);
994 DBG_RETURN(result);
995 }
996 /* }}} */
997
998
999 #define STMT_ID_LENGTH 4
1000
1001 /* {{{ mysqlnd_fetch_row_cursor */
1002 enum_func_status
mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)1003 mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC)
1004 {
1005 enum_func_status ret;
1006 MYSQLND_STMT * s = (MYSQLND_STMT *) param;
1007 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1008 zend_uchar buf[STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */];
1009 MYSQLND_PACKET_ROW * row_packet;
1010
1011 DBG_ENTER("mysqlnd_fetch_stmt_row_cursor");
1012
1013 if (!stmt || !stmt->conn || !result || !result->conn || !result->unbuf) {
1014 DBG_ERR("no statement");
1015 DBG_RETURN(FAIL);
1016 }
1017
1018 DBG_INF_FMT("stmt=%lu flags=%u", stmt->stmt_id, flags);
1019
1020 if (stmt->state < MYSQLND_STMT_USER_FETCHING) {
1021 /* Only initted - error */
1022 SET_CLIENT_ERROR(*stmt->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
1023 mysqlnd_out_of_sync);
1024 DBG_ERR("command out of sync");
1025 DBG_RETURN(FAIL);
1026 }
1027 if (!(row_packet = result->unbuf->row_packet)) {
1028 DBG_RETURN(FAIL);
1029 }
1030
1031 SET_EMPTY_ERROR(*stmt->error_info);
1032 SET_EMPTY_ERROR(*stmt->conn->error_info);
1033
1034 int4store(buf, stmt->stmt_id);
1035 int4store(buf + STMT_ID_LENGTH, 1); /* for now fetch only one row */
1036
1037 if (FAIL == stmt->conn->m->simple_command(stmt->conn, COM_STMT_FETCH, buf, sizeof(buf),
1038 PROT_LAST /* we will handle the response packet*/,
1039 FALSE, TRUE TSRMLS_CC)) {
1040 COPY_CLIENT_ERROR(*stmt->error_info, *stmt->conn->error_info);
1041 DBG_RETURN(FAIL);
1042 }
1043
1044 row_packet->skip_extraction = stmt->result_bind? FALSE:TRUE;
1045
1046 memset(stmt->upsert_status, 0, sizeof(*stmt->upsert_status));
1047 if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
1048 const MYSQLND_RES_METADATA * const meta = result->meta;
1049 unsigned int i, field_count = result->field_count;
1050
1051 if (!row_packet->skip_extraction) {
1052 result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC);
1053
1054 result->unbuf->last_row_data = row_packet->fields;
1055 result->unbuf->last_row_buffer = row_packet->row_buffer;
1056 row_packet->fields = NULL;
1057 row_packet->row_buffer = NULL;
1058
1059 if (PASS != result->unbuf->m.row_decoder(result->unbuf->last_row_buffer,
1060 result->unbuf->last_row_data,
1061 row_packet->field_count,
1062 row_packet->fields_metadata,
1063 result->conn->options->int_and_float_native,
1064 result->conn->stats TSRMLS_CC))
1065 {
1066 DBG_RETURN(FAIL);
1067 }
1068
1069 /* If no result bind, do nothing. We consumed the data */
1070 for (i = 0; i < field_count; i++) {
1071 if (stmt->result_bind[i].bound == TRUE) {
1072 zval *data = result->unbuf->last_row_data[i];
1073 /*
1074 stmt->result_bind[i].zv has been already destructed
1075 in result->unbuf->m.free_last_data()
1076 */
1077 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
1078 zval_dtor(stmt->result_bind[i].zv);
1079 #endif
1080 DBG_INF_FMT("i=%u bound_var=%p type=%u refc=%u", i, stmt->result_bind[i].zv,
1081 Z_TYPE_P(data), Z_REFCOUNT_P(stmt->result_bind[i].zv));
1082 if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data))) {
1083 if ((Z_TYPE_P(data) == IS_STRING) && (meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) {
1084 meta->fields[i].max_length = Z_STRLEN_P(data);
1085 }
1086 stmt->result_bind[i].zv->value = data->value;
1087 /* copied data, thus also the ownership. Thus null data */
1088 ZVAL_NULL(data);
1089 }
1090 }
1091 }
1092 } else {
1093 DBG_INF("skipping extraction");
1094 /*
1095 Data has been allocated and usually result->unbuf->m.free_last_data()
1096 frees it but we can't call this function as it will cause problems with
1097 the bound variables. Thus we need to do part of what it does or Zend will
1098 report leaks.
1099 */
1100 row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC);
1101 row_packet->row_buffer = NULL;
1102 }
1103 /* We asked for one row, the next one should be EOF, eat it */
1104 ret = PACKET_READ(row_packet, result->conn);
1105 if (row_packet->row_buffer) {
1106 row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC);
1107 row_packet->row_buffer = NULL;
1108 }
1109 MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR);
1110
1111 result->unbuf->row_count++;
1112 *fetched_anything = TRUE;
1113 } else {
1114 *fetched_anything = FALSE;
1115
1116 stmt->upsert_status->warning_count =
1117 stmt->conn->upsert_status->warning_count =
1118 row_packet->warning_count;
1119
1120 stmt->upsert_status->server_status =
1121 stmt->conn->upsert_status->server_status =
1122 row_packet->server_status;
1123
1124 result->unbuf->eof_reached = row_packet->eof;
1125 }
1126 stmt->upsert_status->warning_count =
1127 stmt->conn->upsert_status->warning_count =
1128 row_packet->warning_count;
1129 stmt->upsert_status->server_status =
1130 stmt->conn->upsert_status->server_status =
1131 row_packet->server_status;
1132
1133 DBG_INF_FMT("ret=%s fetched=%u server_status=%u warnings=%u eof=%u",
1134 ret == PASS? "PASS":"FAIL", *fetched_anything,
1135 row_packet->server_status, row_packet->warning_count,
1136 result->unbuf->eof_reached);
1137 DBG_RETURN(ret);
1138 }
1139 /* }}} */
1140
1141
1142 /* {{{ mysqlnd_stmt::fetch */
1143 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,fetch)1144 MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const s, zend_bool * const fetched_anything TSRMLS_DC)
1145 {
1146 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1147 enum_func_status ret;
1148 DBG_ENTER("mysqlnd_stmt::fetch");
1149 if (!stmt || !stmt->conn) {
1150 DBG_RETURN(FAIL);
1151 }
1152 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1153
1154 if (!stmt->result ||
1155 stmt->state < MYSQLND_STMT_WAITING_USE_OR_STORE) {
1156 SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1157
1158 DBG_ERR("command out of sync");
1159 DBG_RETURN(FAIL);
1160 } else if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1161 /* Execute only once. We have to free the previous contents of user's bound vars */
1162
1163 stmt->default_rset_handler(s TSRMLS_CC);
1164 }
1165 stmt->state = MYSQLND_STMT_USER_FETCHING;
1166
1167 SET_EMPTY_ERROR(*stmt->error_info);
1168 SET_EMPTY_ERROR(*stmt->conn->error_info);
1169
1170 DBG_INF_FMT("result_bind=%p separated_once=%u", stmt->result_bind, stmt->result_zvals_separated_once);
1171 /*
1172 The user might have not bound any variables for result.
1173 Do the binding once she does it.
1174 */
1175 if (stmt->result_bind && !stmt->result_zvals_separated_once) {
1176 unsigned int i;
1177 /*
1178 mysqlnd_stmt_store_result() has been called free the bind
1179 variables to prevent leaking of their previous content.
1180 */
1181 for (i = 0; i < stmt->result->field_count; i++) {
1182 if (stmt->result_bind[i].bound == TRUE) {
1183 zval_dtor(stmt->result_bind[i].zv);
1184 ZVAL_NULL(stmt->result_bind[i].zv);
1185 }
1186 }
1187 stmt->result_zvals_separated_once = TRUE;
1188 }
1189
1190 ret = stmt->result->m.fetch_row(stmt->result, (void*)s, 0, fetched_anything TSRMLS_CC);
1191 DBG_RETURN(ret);
1192 }
1193 /* }}} */
1194
1195
1196 /* {{{ mysqlnd_stmt::reset */
1197 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,reset)1198 MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const s TSRMLS_DC)
1199 {
1200 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1201 enum_func_status ret = PASS;
1202 zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */];
1203
1204 DBG_ENTER("mysqlnd_stmt::reset");
1205 if (!stmt || !stmt->conn) {
1206 DBG_RETURN(FAIL);
1207 }
1208 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1209
1210 SET_EMPTY_ERROR(*stmt->error_info);
1211 SET_EMPTY_ERROR(*stmt->conn->error_info);
1212
1213 if (stmt->stmt_id) {
1214 MYSQLND_CONN_DATA * conn = stmt->conn;
1215 if (stmt->param_bind) {
1216 unsigned int i;
1217 DBG_INF("resetting long data");
1218 /* Reset Long Data */
1219 for (i = 0; i < stmt->param_count; i++) {
1220 if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
1221 stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1222 }
1223 }
1224 }
1225
1226 s->m->flush(s TSRMLS_CC);
1227
1228 /*
1229 Don't free now, let the result be usable. When the stmt will again be
1230 executed then the result set will be cleaned, the bound variables will
1231 be separated before that.
1232 */
1233
1234 int4store(cmd_buf, stmt->stmt_id);
1235 if (CONN_GET_STATE(conn) == CONN_READY &&
1236 FAIL == (ret = conn->m->simple_command(conn, COM_STMT_RESET, cmd_buf,
1237 sizeof(cmd_buf), PROT_OK_PACKET,
1238 FALSE, TRUE TSRMLS_CC))) {
1239 COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info);
1240 }
1241 *stmt->upsert_status = *conn->upsert_status;
1242 }
1243 DBG_INF(ret == PASS? "PASS":"FAIL");
1244 DBG_RETURN(ret);
1245 }
1246 /* }}} */
1247
1248
1249 /* {{{ mysqlnd_stmt::flush */
1250 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,flush)1251 MYSQLND_METHOD(mysqlnd_stmt, flush)(MYSQLND_STMT * const s TSRMLS_DC)
1252 {
1253 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1254 enum_func_status ret = PASS;
1255
1256 DBG_ENTER("mysqlnd_stmt::flush");
1257 if (!stmt || !stmt->conn) {
1258 DBG_RETURN(FAIL);
1259 }
1260 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1261
1262 if (stmt->stmt_id) {
1263 /*
1264 If the user decided to close the statement right after execute()
1265 We have to call the appropriate use_result() or store_result() and
1266 clean.
1267 */
1268 do {
1269 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1270 DBG_INF("fetching result set header");
1271 stmt->default_rset_handler(s TSRMLS_CC);
1272 stmt->state = MYSQLND_STMT_USER_FETCHING;
1273 }
1274
1275 if (stmt->result) {
1276 DBG_INF("skipping result");
1277 stmt->result->m.skip_result(stmt->result TSRMLS_CC);
1278 }
1279 } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
1280 }
1281 DBG_INF(ret == PASS? "PASS":"FAIL");
1282 DBG_RETURN(ret);
1283 }
1284 /* }}} */
1285
1286
1287 /* {{{ mysqlnd_stmt::send_long_data */
1288 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,send_long_data)1289 MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const s, unsigned int param_no,
1290 const char * const data, unsigned long length TSRMLS_DC)
1291 {
1292 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1293 enum_func_status ret = FAIL;
1294 MYSQLND_CONN_DATA * conn;
1295 zend_uchar * cmd_buf;
1296 enum php_mysqlnd_server_command cmd = COM_STMT_SEND_LONG_DATA;
1297
1298 DBG_ENTER("mysqlnd_stmt::send_long_data");
1299 if (!stmt || !stmt->conn) {
1300 DBG_RETURN(FAIL);
1301 }
1302 DBG_INF_FMT("stmt=%lu param_no=%u data_len=%lu", stmt->stmt_id, param_no, length);
1303
1304 conn = stmt->conn;
1305
1306 SET_EMPTY_ERROR(*stmt->error_info);
1307 SET_EMPTY_ERROR(*stmt->conn->error_info);
1308
1309 if (stmt->state < MYSQLND_STMT_PREPARED) {
1310 SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1311 DBG_ERR("not prepared");
1312 DBG_RETURN(FAIL);
1313 }
1314 if (!stmt->param_bind) {
1315 SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1316 DBG_ERR("command out of sync");
1317 DBG_RETURN(FAIL);
1318 }
1319 if (param_no >= stmt->param_count) {
1320 SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1321 DBG_ERR("invalid param_no");
1322 DBG_RETURN(FAIL);
1323 }
1324 if (stmt->param_bind[param_no].type != MYSQL_TYPE_LONG_BLOB) {
1325 SET_STMT_ERROR(stmt, CR_INVALID_BUFFER_USE, UNKNOWN_SQLSTATE, mysqlnd_not_bound_as_blob);
1326 DBG_ERR("param_no is not of a blob type");
1327 DBG_RETURN(FAIL);
1328 }
1329
1330 /*
1331 XXX: Unfortunately we have to allocate additional buffer to be able the
1332 additional data, which is like a header inside the payload.
1333 This should be optimised, but it will be a pervasive change, so
1334 conn->m->simple_command() will accept not a buffer, but actually MYSQLND_STRING*
1335 terminated by NULL, to send. If the strings are not big, we can collapse them
1336 on the buffer every connection has, but otherwise we will just send them
1337 one by one to the wire.
1338 */
1339
1340 if (CONN_GET_STATE(conn) == CONN_READY) {
1341 size_t packet_len;
1342 cmd_buf = mnd_emalloc(packet_len = STMT_ID_LENGTH + 2 + length);
1343 if (cmd_buf) {
1344 stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED;
1345
1346 int4store(cmd_buf, stmt->stmt_id);
1347 int2store(cmd_buf + STMT_ID_LENGTH, param_no);
1348 memcpy(cmd_buf + STMT_ID_LENGTH + 2, data, length);
1349
1350 /* COM_STMT_SEND_LONG_DATA doesn't send an OK packet*/
1351 ret = conn->m->simple_command(conn, cmd, cmd_buf, packet_len, PROT_LAST , FALSE, TRUE TSRMLS_CC);
1352 mnd_efree(cmd_buf);
1353 if (FAIL == ret) {
1354 COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info);
1355 }
1356 } else {
1357 ret = FAIL;
1358 SET_OOM_ERROR(*stmt->error_info);
1359 SET_OOM_ERROR(*conn->error_info);
1360 }
1361 /*
1362 Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not
1363 sent response packets. According to documentation the only way to get an error
1364 is to have out-of-memory on the server-side. However, that's not true, as if
1365 max_allowed_packet_size is smaller than the chunk being sent to the server, the
1366 latter will complain with an error message. However, normally we don't expect
1367 an error message, thus we continue. When sending the next command, which expects
1368 response we will read the unexpected data and error message will look weird.
1369 Therefore we do non-blocking read to clean the line, if there is a need.
1370 Nevertheless, there is a built-in protection when sending a command packet, that
1371 checks if the line is clear - useful for debug purposes and to be switched off
1372 in release builds.
1373
1374 Maybe we can make it automatic by checking what's the value of
1375 max_allowed_packet_size on the server and resending the data.
1376 */
1377 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
1378 #if HAVE_USLEEP && !defined(PHP_WIN32)
1379 usleep(120000);
1380 #endif
1381 if ((packet_len = conn->net->m.consume_uneaten_data(conn->net, cmd TSRMLS_CC))) {
1382 php_error_docref(NULL TSRMLS_CC, E_WARNING, "There was an error "
1383 "while sending long data. Probably max_allowed_packet_size "
1384 "is smaller than the data. You have to increase it or send "
1385 "smaller chunks of data. Answer was "MYSQLND_SZ_T_SPEC" bytes long.", packet_len);
1386 SET_STMT_ERROR(stmt, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE,
1387 "Server responded to COM_STMT_SEND_LONG_DATA.");
1388 ret = FAIL;
1389 }
1390 #endif
1391 }
1392
1393 DBG_INF(ret == PASS? "PASS":"FAIL");
1394 DBG_RETURN(ret);
1395 }
1396 /* }}} */
1397
1398
1399 /* {{{ mysqlnd_stmt::bind_parameters */
1400 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_parameters)1401 MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * const param_bind TSRMLS_DC)
1402 {
1403 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1404 DBG_ENTER("mysqlnd_stmt::bind_param");
1405 if (!stmt || !stmt->conn) {
1406 DBG_RETURN(FAIL);
1407 }
1408 DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
1409
1410 if (stmt->state < MYSQLND_STMT_PREPARED) {
1411 SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1412 DBG_ERR("not prepared");
1413 if (param_bind) {
1414 s->m->free_parameter_bind(s, param_bind TSRMLS_CC);
1415 }
1416 DBG_RETURN(FAIL);
1417 }
1418
1419 SET_EMPTY_ERROR(*stmt->error_info);
1420 SET_EMPTY_ERROR(*stmt->conn->error_info);
1421
1422 if (stmt->param_count) {
1423 unsigned int i = 0;
1424
1425 if (!param_bind) {
1426 SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, "Re-binding (still) not supported");
1427 DBG_ERR("Re-binding (still) not supported");
1428 DBG_RETURN(FAIL);
1429 } else if (stmt->param_bind) {
1430 DBG_INF("Binding");
1431 /*
1432 There is already result bound.
1433 Forbid for now re-binding!!
1434 */
1435 for (i = 0; i < stmt->param_count; i++) {
1436 /*
1437 We may have the last reference, then call zval_ptr_dtor() or we may leak memory.
1438 Switching from bind_one_parameter to bind_parameters may result in zv being NULL
1439 */
1440 if (stmt->param_bind[i].zv) {
1441 zval_ptr_dtor(&stmt->param_bind[i].zv);
1442 }
1443 }
1444 if (stmt->param_bind != param_bind) {
1445 s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC);
1446 }
1447 }
1448
1449 stmt->param_bind = param_bind;
1450 for (i = 0; i < stmt->param_count; i++) {
1451 /* The client will use stmt_send_long_data */
1452 DBG_INF_FMT("%u is of type %u", i, stmt->param_bind[i].type);
1453 /* Prevent from freeing */
1454 /* Don't update is_ref, or we will leak during conversion */
1455 Z_ADDREF_P(stmt->param_bind[i].zv);
1456 stmt->param_bind[i].flags = 0;
1457 if (stmt->param_bind[i].type == MYSQL_TYPE_LONG_BLOB) {
1458 stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1459 }
1460 }
1461 stmt->send_types_to_server = 1;
1462 }
1463 DBG_INF("PASS");
1464 DBG_RETURN(PASS);
1465 }
1466 /* }}} */
1467
1468
1469 /* {{{ mysqlnd_stmt::bind_one_parameter */
1470 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_parameter)1471 MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter)(MYSQLND_STMT * const s, unsigned int param_no,
1472 zval * const zv, zend_uchar type TSRMLS_DC)
1473 {
1474 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1475 DBG_ENTER("mysqlnd_stmt::bind_one_parameter");
1476 if (!stmt || !stmt->conn) {
1477 DBG_RETURN(FAIL);
1478 }
1479 DBG_INF_FMT("stmt=%lu param_no=%u param_count=%u type=%u", stmt->stmt_id, param_no, stmt->param_count, type);
1480
1481 if (stmt->state < MYSQLND_STMT_PREPARED) {
1482 SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1483 DBG_ERR("not prepared");
1484 DBG_RETURN(FAIL);
1485 }
1486
1487 if (param_no >= stmt->param_count) {
1488 SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1489 DBG_ERR("invalid param_no");
1490 DBG_RETURN(FAIL);
1491 }
1492 SET_EMPTY_ERROR(*stmt->error_info);
1493 SET_EMPTY_ERROR(*stmt->conn->error_info);
1494
1495 if (stmt->param_count) {
1496 if (!stmt->param_bind) {
1497 stmt->param_bind = mnd_pecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND), stmt->persistent);
1498 if (!stmt->param_bind) {
1499 DBG_RETURN(FAIL);
1500 }
1501 }
1502
1503 /* Prevent from freeing */
1504 /* Don't update is_ref, or we will leak during conversion */
1505 Z_ADDREF_P(zv);
1506 DBG_INF("Binding");
1507 /* Release what we had, if we had */
1508 if (stmt->param_bind[param_no].zv) {
1509 zval_ptr_dtor(&stmt->param_bind[param_no].zv);
1510 }
1511 if (type == MYSQL_TYPE_LONG_BLOB) {
1512 /* The client will use stmt_send_long_data */
1513 stmt->param_bind[param_no].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1514 }
1515 stmt->param_bind[param_no].zv = zv;
1516 stmt->param_bind[param_no].type = type;
1517
1518 stmt->send_types_to_server = 1;
1519 }
1520 DBG_INF("PASS");
1521 DBG_RETURN(PASS);
1522 }
1523 /* }}} */
1524
1525
1526 /* {{{ mysqlnd_stmt::refresh_bind_param */
1527 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,refresh_bind_param)1528 MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param)(MYSQLND_STMT * const s TSRMLS_DC)
1529 {
1530 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1531 DBG_ENTER("mysqlnd_stmt::refresh_bind_param");
1532 if (!stmt || !stmt->conn) {
1533 DBG_RETURN(FAIL);
1534 }
1535 DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
1536
1537 if (stmt->state < MYSQLND_STMT_PREPARED) {
1538 SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1539 DBG_ERR("not prepared");
1540 DBG_RETURN(FAIL);
1541 }
1542
1543 SET_EMPTY_ERROR(*stmt->error_info);
1544 SET_EMPTY_ERROR(*stmt->conn->error_info);
1545
1546 if (stmt->param_count) {
1547 stmt->send_types_to_server = 1;
1548 }
1549 DBG_RETURN(PASS);
1550 }
1551 /* }}} */
1552
1553
1554 /* {{{ mysqlnd_stmt::bind_result */
1555 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_result)1556 MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const s,
1557 MYSQLND_RESULT_BIND * const result_bind TSRMLS_DC)
1558 {
1559 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1560 DBG_ENTER("mysqlnd_stmt::bind_result");
1561 if (!stmt || !stmt->conn) {
1562 DBG_RETURN(FAIL);
1563 }
1564 DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
1565
1566 if (stmt->state < MYSQLND_STMT_PREPARED) {
1567 SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1568 if (result_bind) {
1569 s->m->free_result_bind(s, result_bind TSRMLS_CC);
1570 }
1571 DBG_ERR("not prepared");
1572 DBG_RETURN(FAIL);
1573 }
1574
1575 SET_EMPTY_ERROR(*stmt->error_info);
1576 SET_EMPTY_ERROR(*stmt->conn->error_info);
1577
1578 if (stmt->field_count) {
1579 unsigned int i = 0;
1580
1581 if (!result_bind) {
1582 DBG_ERR("no result bind passed");
1583 DBG_RETURN(FAIL);
1584 }
1585
1586 mysqlnd_stmt_separate_result_bind(s TSRMLS_CC);
1587 stmt->result_zvals_separated_once = FALSE;
1588 stmt->result_bind = result_bind;
1589 for (i = 0; i < stmt->field_count; i++) {
1590 /* Prevent from freeing */
1591 Z_ADDREF_P(stmt->result_bind[i].zv);
1592 DBG_INF_FMT("ref of %p = %u", stmt->result_bind[i].zv, Z_REFCOUNT_P(stmt->result_bind[i].zv));
1593 /*
1594 Don't update is_ref !!! it's not our job
1595 Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1596 will fail.
1597 */
1598 stmt->result_bind[i].bound = TRUE;
1599 }
1600 } else if (result_bind) {
1601 s->m->free_result_bind(s, result_bind TSRMLS_CC);
1602 }
1603 DBG_INF("PASS");
1604 DBG_RETURN(PASS);
1605 }
1606 /* }}} */
1607
1608
1609 /* {{{ mysqlnd_stmt::bind_result */
1610 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_result)1611 MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned int param_no TSRMLS_DC)
1612 {
1613 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1614 DBG_ENTER("mysqlnd_stmt::bind_result");
1615 if (!stmt || !stmt->conn) {
1616 DBG_RETURN(FAIL);
1617 }
1618 DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
1619
1620 if (stmt->state < MYSQLND_STMT_PREPARED) {
1621 SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1622 DBG_ERR("not prepared");
1623 DBG_RETURN(FAIL);
1624 }
1625
1626 if (param_no >= stmt->field_count) {
1627 SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1628 DBG_ERR("invalid param_no");
1629 DBG_RETURN(FAIL);
1630 }
1631
1632 SET_EMPTY_ERROR(*stmt->error_info);
1633 SET_EMPTY_ERROR(*stmt->conn->error_info);
1634
1635 if (stmt->field_count) {
1636 mysqlnd_stmt_separate_one_result_bind(s, param_no TSRMLS_CC);
1637 /* Guaranteed is that stmt->result_bind is NULL */
1638 if (!stmt->result_bind) {
1639 stmt->result_bind = mnd_pecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND), stmt->persistent);
1640 } else {
1641 stmt->result_bind = mnd_perealloc(stmt->result_bind, stmt->field_count * sizeof(MYSQLND_RESULT_BIND), stmt->persistent);
1642 }
1643 if (!stmt->result_bind) {
1644 DBG_RETURN(FAIL);
1645 }
1646 ALLOC_INIT_ZVAL(stmt->result_bind[param_no].zv);
1647 /*
1648 Don't update is_ref !!! it's not our job
1649 Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1650 will fail.
1651 */
1652 stmt->result_bind[param_no].bound = TRUE;
1653 }
1654 DBG_INF("PASS");
1655 DBG_RETURN(PASS);
1656 }
1657 /* }}} */
1658
1659
1660 /* {{{ mysqlnd_stmt::insert_id */
1661 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,insert_id)1662 MYSQLND_METHOD(mysqlnd_stmt, insert_id)(const MYSQLND_STMT * const s TSRMLS_DC)
1663 {
1664 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1665 return stmt? stmt->upsert_status->last_insert_id : 0;
1666 }
1667 /* }}} */
1668
1669
1670 /* {{{ mysqlnd_stmt::affected_rows */
1671 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,affected_rows)1672 MYSQLND_METHOD(mysqlnd_stmt, affected_rows)(const MYSQLND_STMT * const s TSRMLS_DC)
1673 {
1674 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1675 return stmt? stmt->upsert_status->affected_rows : 0;
1676 }
1677 /* }}} */
1678
1679
1680 /* {{{ mysqlnd_stmt::num_rows */
1681 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,num_rows)1682 MYSQLND_METHOD(mysqlnd_stmt, num_rows)(const MYSQLND_STMT * const s TSRMLS_DC)
1683 {
1684 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1685 return stmt && stmt->result? mysqlnd_num_rows(stmt->result):0;
1686 }
1687 /* }}} */
1688
1689
1690 /* {{{ mysqlnd_stmt::warning_count */
1691 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,warning_count)1692 MYSQLND_METHOD(mysqlnd_stmt, warning_count)(const MYSQLND_STMT * const s TSRMLS_DC)
1693 {
1694 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1695 return stmt? stmt->upsert_status->warning_count : 0;
1696 }
1697 /* }}} */
1698
1699
1700 /* {{{ mysqlnd_stmt::server_status */
1701 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,server_status)1702 MYSQLND_METHOD(mysqlnd_stmt, server_status)(const MYSQLND_STMT * const s TSRMLS_DC)
1703 {
1704 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1705 return stmt? stmt->upsert_status->server_status : 0;
1706 }
1707 /* }}} */
1708
1709
1710 /* {{{ mysqlnd_stmt::field_count */
1711 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,field_count)1712 MYSQLND_METHOD(mysqlnd_stmt, field_count)(const MYSQLND_STMT * const s TSRMLS_DC)
1713 {
1714 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1715 return stmt? stmt->field_count : 0;
1716 }
1717 /* }}} */
1718
1719
1720 /* {{{ mysqlnd_stmt::param_count */
1721 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,param_count)1722 MYSQLND_METHOD(mysqlnd_stmt, param_count)(const MYSQLND_STMT * const s TSRMLS_DC)
1723 {
1724 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1725 return stmt? stmt->param_count : 0;
1726 }
1727 /* }}} */
1728
1729
1730 /* {{{ mysqlnd_stmt::errno */
1731 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,errno)1732 MYSQLND_METHOD(mysqlnd_stmt, errno)(const MYSQLND_STMT * const s TSRMLS_DC)
1733 {
1734 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1735 return stmt? stmt->error_info->error_no : 0;
1736 }
1737 /* }}} */
1738
1739
1740 /* {{{ mysqlnd_stmt::error */
1741 static const char *
MYSQLND_METHOD(mysqlnd_stmt,error)1742 MYSQLND_METHOD(mysqlnd_stmt, error)(const MYSQLND_STMT * const s TSRMLS_DC)
1743 {
1744 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1745 return stmt? stmt->error_info->error : 0;
1746 }
1747 /* }}} */
1748
1749
1750 /* {{{ mysqlnd_stmt::sqlstate */
1751 static const char *
MYSQLND_METHOD(mysqlnd_stmt,sqlstate)1752 MYSQLND_METHOD(mysqlnd_stmt, sqlstate)(const MYSQLND_STMT * const s TSRMLS_DC)
1753 {
1754 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1755 return stmt && stmt->error_info->sqlstate[0] ? stmt->error_info->sqlstate:MYSQLND_SQLSTATE_NULL;
1756 }
1757 /* }}} */
1758
1759
1760 /* {{{ mysqlnd_stmt::data_seek */
1761 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,data_seek)1762 MYSQLND_METHOD(mysqlnd_stmt, data_seek)(const MYSQLND_STMT * const s, uint64_t row TSRMLS_DC)
1763 {
1764 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1765 return stmt && stmt->result? stmt->result->m.seek_data(stmt->result, row TSRMLS_CC) : FAIL;
1766 }
1767 /* }}} */
1768
1769
1770 /* {{{ mysqlnd_stmt::param_metadata */
1771 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,param_metadata)1772 MYSQLND_METHOD(mysqlnd_stmt, param_metadata)(MYSQLND_STMT * const s TSRMLS_DC)
1773 {
1774 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1775 if (!stmt || !stmt->param_count) {
1776 return NULL;
1777 }
1778 return NULL;
1779 }
1780 /* }}} */
1781
1782
1783 /* {{{ mysqlnd_stmt::result_metadata */
1784 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,result_metadata)1785 MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s TSRMLS_DC)
1786 {
1787 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1788 MYSQLND_RES *result;
1789
1790 DBG_ENTER("mysqlnd_stmt::result_metadata");
1791 if (!stmt) {
1792 DBG_RETURN(NULL);
1793 }
1794 DBG_INF_FMT("stmt=%u field_count=%u", stmt->stmt_id, stmt->field_count);
1795
1796 if (!stmt->field_count || !stmt->conn || !stmt->result || !stmt->result->meta) {
1797 DBG_INF("NULL");
1798 DBG_RETURN(NULL);
1799 }
1800
1801 if (stmt->update_max_length && stmt->result->stored_data) {
1802 /* stored result, we have to update the max_length before we clone the meta data :( */
1803 stmt->result->stored_data->m.initialize_result_set_rest(stmt->result->stored_data, stmt->result->meta, stmt->conn->stats,
1804 stmt->conn->options->int_and_float_native TSRMLS_CC);
1805 }
1806 /*
1807 TODO: This implementation is kind of a hack,
1808 find a better way to do it. In different functions I have put
1809 fuses to check for result->m.fetch_row() being NULL. This should
1810 be handled in a better way.
1811
1812 In the meantime we don't need a zval cache reference for this fake
1813 result set, so we don't get one.
1814 */
1815 do {
1816 result = stmt->conn->m->result_init(stmt->field_count, stmt->persistent TSRMLS_CC);
1817 if (!result) {
1818 break;
1819 }
1820 result->type = MYSQLND_RES_NORMAL;
1821 result->unbuf = mysqlnd_result_unbuffered_init(stmt->field_count, TRUE, result->persistent TSRMLS_CC);
1822 if (!result->unbuf) {
1823 break;
1824 }
1825 result->unbuf->eof_reached = TRUE;
1826 result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC);
1827 if (!result->meta) {
1828 break;
1829 }
1830
1831 DBG_INF_FMT("result=%p", result);
1832 DBG_RETURN(result);
1833 } while (0);
1834
1835 SET_OOM_ERROR(*stmt->conn->error_info);
1836 if (result) {
1837 result->m.free_result(result, TRUE TSRMLS_CC);
1838 }
1839 DBG_RETURN(NULL);
1840 }
1841 /* }}} */
1842
1843
1844 /* {{{ mysqlnd_stmt::attr_set */
1845 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_set)1846 MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const s,
1847 enum mysqlnd_stmt_attr attr_type,
1848 const void * const value TSRMLS_DC)
1849 {
1850 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1851 DBG_ENTER("mysqlnd_stmt::attr_set");
1852 if (!stmt) {
1853 DBG_RETURN(FAIL);
1854 }
1855 DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type);
1856
1857 switch (attr_type) {
1858 case STMT_ATTR_UPDATE_MAX_LENGTH:{
1859 zend_uchar bval = *(zend_uchar *) value;
1860 /*
1861 XXX : libmysql uses my_bool, but mysqli uses ulong as storage on the stack
1862 and mysqlnd won't be used out of the scope of PHP -> use ulong.
1863 */
1864 stmt->update_max_length = bval? TRUE:FALSE;
1865 break;
1866 }
1867 case STMT_ATTR_CURSOR_TYPE: {
1868 unsigned int ival = *(unsigned int *) value;
1869 if (ival > (unsigned long) CURSOR_TYPE_READ_ONLY) {
1870 SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1871 DBG_INF("FAIL");
1872 DBG_RETURN(FAIL);
1873 }
1874 stmt->flags = ival;
1875 break;
1876 }
1877 case STMT_ATTR_PREFETCH_ROWS: {
1878 unsigned int ival = *(unsigned int *) value;
1879 if (ival == 0) {
1880 ival = MYSQLND_DEFAULT_PREFETCH_ROWS;
1881 } else if (ival > 1) {
1882 SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1883 DBG_INF("FAIL");
1884 DBG_RETURN(FAIL);
1885 }
1886 stmt->prefetch_rows = ival;
1887 break;
1888 }
1889 default:
1890 SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1891 DBG_RETURN(FAIL);
1892 }
1893 DBG_INF("PASS");
1894 DBG_RETURN(PASS);
1895 }
1896 /* }}} */
1897
1898
1899 /* {{{ mysqlnd_stmt::attr_get */
1900 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_get)1901 MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s,
1902 enum mysqlnd_stmt_attr attr_type,
1903 void * const value TSRMLS_DC)
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_bool *) value= stmt->update_max_length;
1915 break;
1916 case STMT_ATTR_CURSOR_TYPE:
1917 *(unsigned long *) value= stmt->flags;
1918 break;
1919 case STMT_ATTR_PREFETCH_ROWS:
1920 *(unsigned long *) value= stmt->prefetch_rows;
1921 break;
1922 default:
1923 DBG_RETURN(FAIL);
1924 }
1925 DBG_INF_FMT("value=%lu", value);
1926 DBG_RETURN(PASS);
1927 }
1928 /* }}} */
1929
1930
1931 /* free_result() doesn't actually free stmt->result but only the buffers */
1932 /* {{{ mysqlnd_stmt::free_result */
1933 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,free_result)1934 MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const s TSRMLS_DC)
1935 {
1936 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1937 DBG_ENTER("mysqlnd_stmt::free_result");
1938 if (!stmt || !stmt->conn) {
1939 DBG_RETURN(FAIL);
1940 }
1941 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1942
1943 if (!stmt->result) {
1944 DBG_INF("no result");
1945 DBG_RETURN(PASS);
1946 }
1947
1948 /*
1949 If right after execute() we have to call the appropriate
1950 use_result() or store_result() and clean.
1951 */
1952 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1953 DBG_INF("fetching result set header");
1954 /* Do implicit use_result and then flush the result */
1955 stmt->default_rset_handler = s->m->use_result;
1956 stmt->default_rset_handler(s TSRMLS_CC);
1957 }
1958
1959 if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
1960 DBG_INF("skipping result");
1961 /* Flush if anything is left and unbuffered set */
1962 stmt->result->m.skip_result(stmt->result TSRMLS_CC);
1963 /*
1964 Separate the bound variables, which point to the result set, then
1965 destroy the set.
1966 */
1967 mysqlnd_stmt_separate_result_bind(s TSRMLS_CC);
1968
1969 /* Now we can destroy the result set */
1970 stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC);
1971 }
1972
1973 if (stmt->state > MYSQLND_STMT_PREPARED) {
1974 /* As the buffers have been freed, we should go back to PREPARED */
1975 stmt->state = MYSQLND_STMT_PREPARED;
1976 }
1977
1978 /* Line is free! */
1979 CONN_SET_STATE(stmt->conn, CONN_READY);
1980
1981 DBG_RETURN(PASS);
1982 }
1983 /* }}} */
1984
1985
1986 /* {{{ mysqlnd_stmt_separate_result_bind */
1987 static void
mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s TSRMLS_DC)1988 mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s TSRMLS_DC)
1989 {
1990 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1991 unsigned int i;
1992
1993 DBG_ENTER("mysqlnd_stmt_separate_result_bind");
1994 if (!stmt) {
1995 DBG_VOID_RETURN;
1996 }
1997 DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u", stmt->stmt_id, stmt->result_bind, stmt->field_count);
1998
1999 if (!stmt->result_bind) {
2000 DBG_VOID_RETURN;
2001 }
2002
2003 /*
2004 Because only the bound variables can point to our internal buffers, then
2005 separate or free only them. Free is possible because the user could have
2006 lost reference.
2007 */
2008 for (i = 0; i < stmt->field_count; i++) {
2009 /* Let's try with no cache */
2010 if (stmt->result_bind[i].bound == TRUE) {
2011 DBG_INF_FMT("%u has refcount=%u", i, Z_REFCOUNT_P(stmt->result_bind[i].zv));
2012 /*
2013 We have to separate the actual zval value of the bound
2014 variable from our allocated zvals or we will face double-free
2015 */
2016 if (Z_REFCOUNT_P(stmt->result_bind[i].zv) > 1) {
2017 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
2018 zval_copy_ctor(stmt->result_bind[i].zv);
2019 #endif
2020 zval_ptr_dtor(&stmt->result_bind[i].zv);
2021 } else {
2022 /*
2023 If it is a string, what is pointed will be freed
2024 later in free_result(). We need to remove the variable to
2025 which the user has lost reference.
2026 */
2027 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
2028 ZVAL_NULL(stmt->result_bind[i].zv);
2029 #endif
2030 zval_ptr_dtor(&stmt->result_bind[i].zv);
2031 }
2032 }
2033 }
2034 s->m->free_result_bind(s, stmt->result_bind TSRMLS_CC);
2035 stmt->result_bind = NULL;
2036
2037 DBG_VOID_RETURN;
2038 }
2039 /* }}} */
2040
2041
2042 /* {{{ mysqlnd_stmt_separate_one_result_bind */
2043 static void
mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s,unsigned int param_no TSRMLS_DC)2044 mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s, unsigned int param_no TSRMLS_DC)
2045 {
2046 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2047 DBG_ENTER("mysqlnd_stmt_separate_one_result_bind");
2048 if (!stmt) {
2049 DBG_VOID_RETURN;
2050 }
2051 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);
2052
2053 if (!stmt->result_bind) {
2054 DBG_VOID_RETURN;
2055 }
2056
2057 /*
2058 Because only the bound variables can point to our internal buffers, then
2059 separate or free only them. Free is possible because the user could have
2060 lost reference.
2061 */
2062 /* Let's try with no cache */
2063 if (stmt->result_bind[param_no].bound == TRUE) {
2064 DBG_INF_FMT("%u has refcount=%u", param_no, Z_REFCOUNT_P(stmt->result_bind[param_no].zv));
2065 /*
2066 We have to separate the actual zval value of the bound
2067 variable from our allocated zvals or we will face double-free
2068 */
2069 if (Z_REFCOUNT_P(stmt->result_bind[param_no].zv) > 1) {
2070 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
2071 zval_copy_ctor(stmt->result_bind[param_no].zv);
2072 #endif
2073 zval_ptr_dtor(&stmt->result_bind[param_no].zv);
2074 } else {
2075 /*
2076 If it is a string, what is pointed will be freed
2077 later in free_result(). We need to remove the variable to
2078 which the user has lost reference.
2079 */
2080 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
2081 ZVAL_NULL(stmt->result_bind[param_no].zv);
2082 #endif
2083 zval_ptr_dtor(&stmt->result_bind[param_no].zv);
2084 }
2085 }
2086
2087 DBG_VOID_RETURN;
2088 }
2089 /* }}} */
2090
2091
2092 /* {{{ mysqlnd_stmt::free_stmt_result */
2093 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_result)2094 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)(MYSQLND_STMT * const s TSRMLS_DC)
2095 {
2096 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2097 DBG_ENTER("mysqlnd_stmt::free_stmt_result");
2098 if (!stmt) {
2099 DBG_VOID_RETURN;
2100 }
2101
2102 /*
2103 First separate the bound variables, which point to the result set, then
2104 destroy the set.
2105 */
2106 mysqlnd_stmt_separate_result_bind(s TSRMLS_CC);
2107 /* Not every statement has a result set attached */
2108 if (stmt->result) {
2109 stmt->result->m.free_result_internal(stmt->result TSRMLS_CC);
2110 stmt->result = NULL;
2111 }
2112 if (stmt->error_info->error_list) {
2113 zend_llist_clean(stmt->error_info->error_list);
2114 mnd_pefree(stmt->error_info->error_list, s->persistent);
2115 stmt->error_info->error_list = NULL;
2116 }
2117
2118 DBG_VOID_RETURN;
2119 }
2120 /* }}} */
2121
2122
2123 /* {{{ mysqlnd_stmt::free_stmt_content */
2124 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_content)2125 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content)(MYSQLND_STMT * const s TSRMLS_DC)
2126 {
2127 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2128 DBG_ENTER("mysqlnd_stmt::free_stmt_content");
2129 if (!stmt) {
2130 DBG_VOID_RETURN;
2131 }
2132 DBG_INF_FMT("stmt=%lu param_bind=%p param_count=%u", stmt->stmt_id, stmt->param_bind, stmt->param_count);
2133
2134 /* Destroy the input bind */
2135 if (stmt->param_bind) {
2136 unsigned int i;
2137 /*
2138 Because only the bound variables can point to our internal buffers, then
2139 separate or free only them. Free is possible because the user could have
2140 lost reference.
2141 */
2142 for (i = 0; i < stmt->param_count; i++) {
2143 /*
2144 If bind_one_parameter was used, but not everything was
2145 bound and nothing was fetched, then some `zv` could be NULL
2146 */
2147 if (stmt->param_bind[i].zv) {
2148 zval_ptr_dtor(&stmt->param_bind[i].zv);
2149 }
2150 }
2151 s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC);
2152 stmt->param_bind = NULL;
2153 }
2154
2155 s->m->free_stmt_result(s TSRMLS_CC);
2156 DBG_VOID_RETURN;
2157 }
2158 /* }}} */
2159
2160
2161 /* {{{ mysqlnd_stmt::net_close */
2162 static enum_func_status
MYSQLND_METHOD_PRIVATE(mysqlnd_stmt,net_close)2163 MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close)(MYSQLND_STMT * const s, zend_bool implicit TSRMLS_DC)
2164 {
2165 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2166 MYSQLND_CONN_DATA * conn;
2167 zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */];
2168 enum_mysqlnd_collected_stats statistic = STAT_LAST;
2169
2170 DBG_ENTER("mysqlnd_stmt::net_close");
2171 if (!stmt || !stmt->conn) {
2172 DBG_RETURN(FAIL);
2173 }
2174 DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
2175
2176 conn = stmt->conn;
2177
2178 SET_EMPTY_ERROR(*stmt->error_info);
2179 SET_EMPTY_ERROR(*stmt->conn->error_info);
2180
2181 /*
2182 If the user decided to close the statement right after execute()
2183 We have to call the appropriate use_result() or store_result() and
2184 clean.
2185 */
2186 do {
2187 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
2188 DBG_INF("fetching result set header");
2189 stmt->default_rset_handler(s TSRMLS_CC);
2190 stmt->state = MYSQLND_STMT_USER_FETCHING;
2191 }
2192
2193 /* unbuffered set not fetched to the end ? Clean the line */
2194 if (stmt->result) {
2195 DBG_INF("skipping result");
2196 stmt->result->m.skip_result(stmt->result TSRMLS_CC);
2197 }
2198 } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
2199 /*
2200 After this point we are allowed to free the result set,
2201 as we have cleaned the line
2202 */
2203 if (stmt->stmt_id) {
2204 MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_FREE_RESULT_IMPLICIT:
2205 STAT_FREE_RESULT_EXPLICIT);
2206
2207 int4store(cmd_buf, stmt->stmt_id);
2208 if (CONN_GET_STATE(conn) == CONN_READY &&
2209 FAIL == conn->m->simple_command(conn, COM_STMT_CLOSE, cmd_buf, sizeof(cmd_buf),
2210 PROT_LAST /* COM_STMT_CLOSE doesn't send an OK packet*/,
2211 FALSE, TRUE TSRMLS_CC)) {
2212 COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info);
2213 DBG_RETURN(FAIL);
2214 }
2215 }
2216 switch (stmt->execute_count) {
2217 case 0:
2218 statistic = STAT_PS_PREPARED_NEVER_EXECUTED;
2219 break;
2220 case 1:
2221 statistic = STAT_PS_PREPARED_ONCE_USED;
2222 break;
2223 default:
2224 break;
2225 }
2226 if (statistic != STAT_LAST) {
2227 MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
2228 }
2229
2230 if (stmt->execute_cmd_buffer.buffer) {
2231 mnd_pefree(stmt->execute_cmd_buffer.buffer, stmt->persistent);
2232 stmt->execute_cmd_buffer.buffer = NULL;
2233 }
2234
2235 s->m->free_stmt_content(s TSRMLS_CC);
2236
2237 if (stmt->conn) {
2238 stmt->conn->m->free_reference(stmt->conn TSRMLS_CC);
2239 stmt->conn = NULL;
2240 }
2241
2242 DBG_RETURN(PASS);
2243 }
2244 /* }}} */
2245
2246 /* {{{ mysqlnd_stmt::dtor */
2247 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,dtor)2248 MYSQLND_METHOD(mysqlnd_stmt, dtor)(MYSQLND_STMT * const s, zend_bool implicit TSRMLS_DC)
2249 {
2250 MYSQLND_STMT_DATA * stmt = (s != NULL) ? s->data:NULL;
2251 enum_func_status ret = FAIL;
2252 zend_bool persistent = (s != NULL) ? s->persistent : 0;
2253
2254 DBG_ENTER("mysqlnd_stmt::dtor");
2255 if (stmt) {
2256 DBG_INF_FMT("stmt=%p", stmt);
2257
2258 MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_STMT_CLOSE_IMPLICIT:
2259 STAT_STMT_CLOSE_EXPLICIT);
2260
2261 ret = s->m->net_close(s, implicit TSRMLS_CC);
2262 mnd_pefree(stmt, persistent);
2263 }
2264 mnd_pefree(s, persistent);
2265
2266 DBG_INF(ret == PASS? "PASS":"FAIL");
2267 DBG_RETURN(ret);
2268 }
2269 /* }}} */
2270
2271
2272 /* {{{ mysqlnd_stmt::alloc_param_bind */
2273 static MYSQLND_PARAM_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_param_bind)2274 MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind)(MYSQLND_STMT * const s TSRMLS_DC)
2275 {
2276 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2277 DBG_ENTER("mysqlnd_stmt::alloc_param_bind");
2278 if (!stmt) {
2279 DBG_RETURN(NULL);
2280 }
2281 DBG_RETURN(mnd_pecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND), stmt->persistent));
2282 }
2283 /* }}} */
2284
2285
2286 /* {{{ mysqlnd_stmt::alloc_result_bind */
2287 static MYSQLND_RESULT_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_result_bind)2288 MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind)(MYSQLND_STMT * const s TSRMLS_DC)
2289 {
2290 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2291 DBG_ENTER("mysqlnd_stmt::alloc_result_bind");
2292 if (!stmt) {
2293 DBG_RETURN(NULL);
2294 }
2295 DBG_RETURN(mnd_pecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND), stmt->persistent));
2296 }
2297 /* }}} */
2298
2299
2300 /* {{{ param_bind::free_parameter_bind */
2301 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_parameter_bind)2302 MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * param_bind TSRMLS_DC)
2303 {
2304 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2305 if (stmt) {
2306 mnd_pefree(param_bind, stmt->persistent);
2307 }
2308 }
2309 /* }}} */
2310
2311
2312 /* {{{ mysqlnd_stmt::free_result_bind */
2313 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_result_bind)2314 MYSQLND_METHOD(mysqlnd_stmt, free_result_bind)(MYSQLND_STMT * const s, MYSQLND_RESULT_BIND * result_bind TSRMLS_DC)
2315 {
2316 MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2317 if (stmt) {
2318 mnd_pefree(result_bind, stmt->persistent);
2319 }
2320 }
2321 /* }}} */
2322
2323
2324
2325 MYSQLND_CLASS_METHODS_START(mysqlnd_stmt)
2326 MYSQLND_METHOD(mysqlnd_stmt, prepare),
2327 MYSQLND_METHOD(mysqlnd_stmt, execute),
2328 MYSQLND_METHOD(mysqlnd_stmt, use_result),
2329 MYSQLND_METHOD(mysqlnd_stmt, store_result),
2330 MYSQLND_METHOD(mysqlnd_stmt, get_result),
2331 MYSQLND_METHOD(mysqlnd_stmt, more_results),
2332 MYSQLND_METHOD(mysqlnd_stmt, next_result),
2333 MYSQLND_METHOD(mysqlnd_stmt, free_result),
2334 MYSQLND_METHOD(mysqlnd_stmt, data_seek),
2335 MYSQLND_METHOD(mysqlnd_stmt, reset),
2336 MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close),
2337 MYSQLND_METHOD(mysqlnd_stmt, dtor),
2338
2339 MYSQLND_METHOD(mysqlnd_stmt, fetch),
2340
2341 MYSQLND_METHOD(mysqlnd_stmt, bind_parameters),
2342 MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter),
2343 MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param),
2344 MYSQLND_METHOD(mysqlnd_stmt, bind_result),
2345 MYSQLND_METHOD(mysqlnd_stmt, bind_one_result),
2346 MYSQLND_METHOD(mysqlnd_stmt, send_long_data),
2347 MYSQLND_METHOD(mysqlnd_stmt, param_metadata),
2348 MYSQLND_METHOD(mysqlnd_stmt, result_metadata),
2349
2350 MYSQLND_METHOD(mysqlnd_stmt, insert_id),
2351 MYSQLND_METHOD(mysqlnd_stmt, affected_rows),
2352 MYSQLND_METHOD(mysqlnd_stmt, num_rows),
2353
2354 MYSQLND_METHOD(mysqlnd_stmt, param_count),
2355 MYSQLND_METHOD(mysqlnd_stmt, field_count),
2356 MYSQLND_METHOD(mysqlnd_stmt, warning_count),
2357
2358 MYSQLND_METHOD(mysqlnd_stmt, errno),
2359 MYSQLND_METHOD(mysqlnd_stmt, error),
2360 MYSQLND_METHOD(mysqlnd_stmt, sqlstate),
2361
2362 MYSQLND_METHOD(mysqlnd_stmt, attr_get),
2363 MYSQLND_METHOD(mysqlnd_stmt, attr_set),
2364
2365
2366 MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind),
2367 MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind),
2368 MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind),
2369 MYSQLND_METHOD(mysqlnd_stmt, free_result_bind),
2370 MYSQLND_METHOD(mysqlnd_stmt, server_status),
2371 mysqlnd_stmt_execute_generate_request,
2372 mysqlnd_stmt_execute_parse_response,
2373 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content),
2374 MYSQLND_METHOD(mysqlnd_stmt, flush),
2375 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)
2376 MYSQLND_CLASS_METHODS_END;
2377
2378
2379 /* {{{ _mysqlnd_stmt_init */
2380 MYSQLND_STMT *
_mysqlnd_stmt_init(MYSQLND_CONN_DATA * const conn TSRMLS_DC)2381 _mysqlnd_stmt_init(MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2382 {
2383 MYSQLND_STMT * ret;
2384 DBG_ENTER("_mysqlnd_stmt_init");
2385 ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_prepared_statement(conn TSRMLS_CC);
2386 DBG_RETURN(ret);
2387 }
2388 /* }}} */
2389
2390
2391 /* {{{ _mysqlnd_init_ps_subsystem */
_mysqlnd_init_ps_subsystem()2392 void _mysqlnd_init_ps_subsystem()
2393 {
2394 mysqlnd_stmt_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_stmt));
2395 _mysqlnd_init_ps_fetch_subsystem();
2396 }
2397 /* }}} */
2398
2399
2400 /*
2401 * Local variables:
2402 * tab-width: 4
2403 * c-basic-offset: 4
2404 * End:
2405 * vim600: noet sw=4 ts=4 fdm=marker
2406 * vim<600: noet sw=4 ts=4
2407 */
2408