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