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