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