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