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