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