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 stmt->result may be freed and nullified by free_stmt_result, transitively called from flush.
656 */
657 if (stmt->result) {
658 stmt->result->m.free_result_buffers(stmt->result);
659 }
660
661 stmt->state = MYSQLND_STMT_PREPARED;
662 } else if (stmt->state < MYSQLND_STMT_PREPARED) {
663 /* Only initted - error */
664 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
665 DBG_INF("FAIL");
666 DBG_RETURN(FAIL);
667 }
668
669 if (stmt->param_count) {
670 unsigned int i, not_bound = 0;
671 if (!stmt->param_bind) {
672 SET_CLIENT_ERROR(stmt->error_info, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, "No data supplied for parameters in prepared statement");
673 DBG_INF("FAIL");
674 DBG_RETURN(FAIL);
675 }
676 for (i = 0; i < stmt->param_count; i++) {
677 if (Z_ISUNDEF(stmt->param_bind[i].zv)) {
678 not_bound++;
679 }
680 }
681 if (not_bound) {
682 char * msg;
683 mnd_sprintf(&msg, 0, "No data supplied for %u parameter%s in prepared statement",
684 not_bound, not_bound>1 ?"s":"");
685 SET_CLIENT_ERROR(stmt->error_info, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, msg);
686 if (msg) {
687 mnd_sprintf_free(msg);
688 }
689 DBG_INF("FAIL");
690 DBG_RETURN(FAIL);
691 }
692 }
693 ret = s->m->generate_execute_request(s, &request, &request_len, &free_request);
694 if (ret == PASS) {
695 const MYSQLND_CSTRING payload = {(const char*) request, request_len};
696
697 ret = conn->command->stmt_execute(conn, payload);
698 } else {
699 SET_CLIENT_ERROR(stmt->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Couldn't generate the request. Possibly OOM.");
700 }
701
702 if (free_request) {
703 mnd_efree(request);
704 }
705
706 if (ret == FAIL) {
707 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
708 DBG_INF("FAIL");
709 DBG_RETURN(FAIL);
710 }
711 stmt->execute_count++;
712
713 DBG_RETURN(PASS);
714 }
715 /* }}} */
716
717
718 /* {{{ mysqlnd_stmt::use_result */
719 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,use_result)720 MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s)
721 {
722 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
723 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
724 MYSQLND_RES * result;
725
726 DBG_ENTER("mysqlnd_stmt::use_result");
727 if (!stmt || !conn || !stmt->result) {
728 DBG_RETURN(NULL);
729 }
730 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
731
732 if (!stmt->field_count || !mysqlnd_stmt_check_state(stmt)) {
733 SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
734 DBG_ERR("command out of sync");
735 DBG_RETURN(NULL);
736 }
737
738 SET_EMPTY_ERROR(stmt->error_info);
739
740 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_PS_UNBUFFERED_SETS);
741 result = stmt->result;
742
743 result->m.use_result(stmt->result, stmt);
744 if (stmt->cursor_exists) {
745 result->unbuf->m.fetch_row = mysqlnd_fetch_stmt_row_cursor;
746 }
747 stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
748
749 DBG_INF_FMT("%p", result);
750 DBG_RETURN(result);
751 }
752 /* }}} */
753
754
755 /* {{{ mysqlnd_fetch_row_cursor */
756 enum_func_status
mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result,zval ** row_ptr,const unsigned int flags,bool * fetched_anything)757 mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, zval **row_ptr, const unsigned int flags, bool * fetched_anything)
758 {
759 enum_func_status ret;
760 MYSQLND_STMT_DATA * stmt = result->unbuf->stmt;
761 MYSQLND_CONN_DATA * conn = stmt->conn;
762 MYSQLND_PACKET_ROW * row_packet;
763 void *checkpoint;
764
765 DBG_ENTER("mysqlnd_fetch_stmt_row_cursor");
766
767 if (!stmt->conn || !result->conn) {
768 DBG_ERR("no statement");
769 DBG_RETURN(FAIL);
770 }
771 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " flags=%u", stmt->stmt_id, flags);
772
773 if (stmt->state < MYSQLND_STMT_USER_FETCHING) {
774 /* Only initted - error */
775 SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
776 DBG_ERR("command out of sync");
777 DBG_RETURN(FAIL);
778 }
779 if (!(row_packet = result->unbuf->row_packet)) {
780 DBG_RETURN(FAIL);
781 }
782
783 SET_EMPTY_ERROR(stmt->error_info);
784 SET_EMPTY_ERROR(conn->error_info);
785
786 /* for now fetch only one row */
787 if (mysqlnd_stmt_send_cursor_fetch_command(stmt, 1) == FAIL) {
788 DBG_RETURN(FAIL);
789 }
790
791 checkpoint = result->memory_pool->checkpoint;
792 mysqlnd_mempool_save_state(result->memory_pool);
793
794 UPSERT_STATUS_RESET(stmt->upsert_status);
795 if (PASS == (ret = PACKET_READ(conn, row_packet)) && !row_packet->eof) {
796 if (row_ptr) {
797 result->unbuf->last_row_buffer = row_packet->row_buffer;
798 row_packet->row_buffer.ptr = NULL;
799 *row_ptr = result->row_data;
800
801 if (PASS != result->unbuf->m.row_decoder(&result->unbuf->last_row_buffer,
802 result->row_data,
803 row_packet->field_count,
804 row_packet->fields_metadata,
805 conn->options->int_and_float_native,
806 conn->stats))
807 {
808 mysqlnd_mempool_restore_state(result->memory_pool);
809 result->memory_pool->checkpoint = checkpoint;
810 DBG_RETURN(FAIL);
811 }
812 } else {
813 DBG_INF("skipping extraction");
814 row_packet->row_buffer.ptr = NULL;
815 }
816 /* We asked for one row, the next one should be EOF, eat it */
817 ret = PACKET_READ(conn, row_packet);
818 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR);
819
820 result->unbuf->row_count++;
821 *fetched_anything = TRUE;
822 } else {
823 *fetched_anything = FALSE;
824 UPSERT_STATUS_SET_WARNINGS(stmt->upsert_status, row_packet->warning_count);
825 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
826
827 UPSERT_STATUS_SET_SERVER_STATUS(stmt->upsert_status, row_packet->server_status);
828 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
829
830 result->unbuf->eof_reached = row_packet->eof;
831 }
832 UPSERT_STATUS_SET_WARNINGS(stmt->upsert_status, row_packet->warning_count);
833 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
834
835 UPSERT_STATUS_SET_SERVER_STATUS(stmt->upsert_status, row_packet->server_status);
836 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
837
838 mysqlnd_mempool_restore_state(result->memory_pool);
839 result->memory_pool->checkpoint = checkpoint;
840
841 DBG_INF_FMT("ret=%s fetched=%u server_status=%u warnings=%u eof=%u",
842 ret == PASS? "PASS":"FAIL", *fetched_anything,
843 row_packet->server_status, row_packet->warning_count,
844 result->unbuf->eof_reached);
845 DBG_RETURN(ret);
846 }
847 /* }}} */
848
849
850 /* {{{ mysqlnd_stmt::fetch */
851 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,fetch)852 MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const s, bool * const fetched_anything)
853 {
854 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
855 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
856 enum_func_status ret;
857 DBG_ENTER("mysqlnd_stmt::fetch");
858 if (!stmt || !stmt->conn) {
859 DBG_RETURN(FAIL);
860 }
861 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
862
863 if (!stmt->result || stmt->state < MYSQLND_STMT_WAITING_USE_OR_STORE) {
864 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
865 DBG_ERR("command out of sync");
866 DBG_RETURN(FAIL);
867 } else if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
868 /* Execute only once. We have to free the previous contents of user's bound vars */
869
870 stmt->default_rset_handler(s);
871 }
872 stmt->state = MYSQLND_STMT_USER_FETCHING;
873
874 SET_EMPTY_ERROR(stmt->error_info);
875 SET_EMPTY_ERROR(conn->error_info);
876
877 if (stmt->result_bind) {
878 zval *row_data;
879 ret = stmt->result->m.fetch_row(stmt->result, &row_data, 0, fetched_anything);
880 if (ret == PASS && *fetched_anything) {
881 unsigned field_count = stmt->result->field_count;
882 for (unsigned i = 0; i < field_count; i++) {
883 zval *resultzv = &stmt->result_bind[i].zv;
884 if (stmt->result_bind[i].bound == TRUE) {
885 DBG_INF_FMT("i=%u type=%u", i, Z_TYPE(row_data[i]));
886 ZEND_TRY_ASSIGN_VALUE_EX(resultzv, &row_data[i], 0);
887 } else {
888 zval_ptr_dtor_nogc(&row_data[i]);
889 }
890 }
891 }
892 } else {
893 ret = stmt->result->m.fetch_row(stmt->result, NULL, 0, fetched_anything);
894 }
895 DBG_RETURN(ret);
896 }
897 /* }}} */
898
899
900 /* {{{ mysqlnd_stmt::reset */
901 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,reset)902 MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const s)
903 {
904 enum_func_status ret = PASS;
905 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
906 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
907
908 DBG_ENTER("mysqlnd_stmt::reset");
909 if (!stmt || !conn) {
910 DBG_RETURN(FAIL);
911 }
912 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
913
914 SET_EMPTY_ERROR(stmt->error_info);
915 SET_EMPTY_ERROR(conn->error_info);
916
917 if (stmt->stmt_id) {
918 MYSQLND_CONN_DATA * conn = stmt->conn;
919 if (stmt->param_bind) {
920 unsigned int i;
921 DBG_INF("resetting long data");
922 /* Reset Long Data */
923 for (i = 0; i < stmt->param_count; i++) {
924 if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
925 stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
926 }
927 }
928 }
929
930 s->m->flush(s);
931
932 /*
933 Don't free now, let the result be usable. When the stmt will again be
934 executed then the result set will be cleaned, the bound variables will
935 be separated before that.
936 */
937
938 if (GET_CONNECTION_STATE(&conn->state) == CONN_READY) {
939 size_t stmt_id = stmt->stmt_id;
940
941 ret = stmt->conn->command->stmt_reset(stmt->conn, stmt_id);
942 if (ret == FAIL) {
943 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
944 }
945 }
946 *stmt->upsert_status = *conn->upsert_status;
947 }
948 DBG_INF(ret == PASS? "PASS":"FAIL");
949 DBG_RETURN(ret);
950 }
951 /* }}} */
952
953
954 /* {{{ mysqlnd_stmt::flush */
955 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,flush)956 MYSQLND_METHOD(mysqlnd_stmt, flush)(MYSQLND_STMT * const s)
957 {
958 enum_func_status ret = PASS;
959 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
960 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
961
962 DBG_ENTER("mysqlnd_stmt::flush");
963 if (!stmt || !conn) {
964 DBG_RETURN(FAIL);
965 }
966 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
967
968 if (stmt->stmt_id) {
969 /*
970 If the user decided to close the statement right after execute()
971 We have to call the appropriate use_result() or store_result() and
972 clean.
973 */
974 do {
975 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
976 DBG_INF("fetching result set header");
977 stmt->default_rset_handler(s);
978 stmt->state = MYSQLND_STMT_USER_FETCHING;
979 }
980
981 if (stmt->result) {
982 DBG_INF("skipping result");
983 stmt->result->m.skip_result(stmt->result);
984 }
985 } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
986 }
987 DBG_INF(ret == PASS? "PASS":"FAIL");
988 DBG_RETURN(ret);
989 }
990 /* }}} */
991
992
993 /* {{{ mysqlnd_stmt::send_long_data */
994 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,send_long_data)995 MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const s, unsigned int param_no,
996 const char * const data, zend_ulong data_length)
997 {
998 enum_func_status ret = FAIL;
999 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1000 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1001 zend_uchar * cmd_buf;
1002
1003 DBG_ENTER("mysqlnd_stmt::send_long_data");
1004 if (!stmt || !conn) {
1005 DBG_RETURN(FAIL);
1006 }
1007 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_no=%u data_len=" ZEND_ULONG_FMT, stmt->stmt_id, param_no, data_length);
1008
1009 SET_EMPTY_ERROR(stmt->error_info);
1010 SET_EMPTY_ERROR(conn->error_info);
1011
1012 if (stmt->state < MYSQLND_STMT_PREPARED) {
1013 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1014 DBG_ERR("not prepared");
1015 DBG_RETURN(FAIL);
1016 }
1017 if (!stmt->param_bind) {
1018 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1019 DBG_ERR("command out of sync");
1020 DBG_RETURN(FAIL);
1021 }
1022 if (param_no >= stmt->param_count) {
1023 SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1024 DBG_ERR("invalid param_no");
1025 DBG_RETURN(FAIL);
1026 }
1027 if (stmt->param_bind[param_no].type != MYSQL_TYPE_LONG_BLOB) {
1028 SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_BUFFER_USE, UNKNOWN_SQLSTATE, mysqlnd_not_bound_as_blob);
1029 DBG_ERR("param_no is not of a blob type");
1030 DBG_RETURN(FAIL);
1031 }
1032
1033 if (GET_CONNECTION_STATE(&conn->state) == CONN_READY) {
1034 const size_t packet_len = MYSQLND_STMT_ID_LENGTH + 2 + data_length;
1035 cmd_buf = mnd_emalloc(packet_len);
1036 if (cmd_buf) {
1037 stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED;
1038
1039 int4store(cmd_buf, stmt->stmt_id);
1040 int2store(cmd_buf + MYSQLND_STMT_ID_LENGTH, param_no);
1041 memcpy(cmd_buf + MYSQLND_STMT_ID_LENGTH + 2, data, data_length);
1042
1043 /* COM_STMT_SEND_LONG_DATA doesn't acknowledge with an OK packet */
1044 {
1045 const MYSQLND_CSTRING payload = {(const char *) cmd_buf, packet_len};
1046
1047 ret = conn->command->stmt_send_long_data(conn, payload);
1048 if (ret == FAIL) {
1049 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
1050 }
1051 }
1052
1053 mnd_efree(cmd_buf);
1054 } else {
1055 ret = FAIL;
1056 SET_OOM_ERROR(stmt->error_info);
1057 SET_OOM_ERROR(conn->error_info);
1058 }
1059 /*
1060 Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not
1061 sent response packets. According to documentation the only way to get an error
1062 is to have out-of-memory on the server-side. However, that's not true, as if
1063 max_allowed_packet_size is smaller than the chunk being sent to the server, the
1064 latter will complain with an error message. However, normally we don't expect
1065 an error message, thus we continue. When sending the next command, which expects
1066 response we will read the unexpected data and error message will look weird.
1067 Therefore we do non-blocking read to clean the line, if there is a need.
1068 Nevertheless, there is a built-in protection when sending a command packet, that
1069 checks if the line is clear - useful for debug purposes and to be switched off
1070 in release builds.
1071
1072 Maybe we can make it automatic by checking what's the value of
1073 max_allowed_packet_size on the server and resending the data.
1074 */
1075 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
1076 #if defined(HAVE_USLEEP) && !defined(PHP_WIN32)
1077 usleep(120000);
1078 #endif
1079 if ((packet_len = conn->protocol_frame_codec->m.consume_uneaten_data(conn->protocol_frame_codec, COM_STMT_SEND_LONG_DATA))) {
1080 php_error_docref(NULL, E_WARNING, "There was an error "
1081 "while sending long data. Probably max_allowed_packet_size "
1082 "is smaller than the data. You have to increase it or send "
1083 "smaller chunks of data. Answer was %zu bytes long.", packet_len);
1084 SET_CLIENT_ERROR(stmt->error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE,
1085 "Server responded to COM_STMT_SEND_LONG_DATA.");
1086 ret = FAIL;
1087 }
1088 #endif
1089 }
1090
1091 DBG_INF(ret == PASS? "PASS":"FAIL");
1092 DBG_RETURN(ret);
1093 }
1094 /* }}} */
1095
1096
1097 /* {{{ mysqlnd_stmt::bind_parameters */
1098 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_parameters)1099 MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * const param_bind)
1100 {
1101 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1102 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1103
1104 DBG_ENTER("mysqlnd_stmt::bind_param");
1105 if (!stmt || !conn) {
1106 DBG_RETURN(FAIL);
1107 }
1108 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_count=%u", stmt->stmt_id, stmt->param_count);
1109
1110 if (stmt->state < MYSQLND_STMT_PREPARED) {
1111 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1112 DBG_ERR("not prepared");
1113 if (param_bind) {
1114 s->m->free_parameter_bind(s, param_bind);
1115 }
1116 DBG_RETURN(FAIL);
1117 }
1118
1119 SET_EMPTY_ERROR(stmt->error_info);
1120 SET_EMPTY_ERROR(conn->error_info);
1121
1122 if (stmt->param_count) {
1123 unsigned int i = 0;
1124
1125 if (!param_bind) {
1126 SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, "Re-binding (still) not supported");
1127 DBG_ERR("Re-binding (still) not supported");
1128 DBG_RETURN(FAIL);
1129 } else if (stmt->param_bind) {
1130 DBG_INF("Binding");
1131 /*
1132 There is already result bound.
1133 Forbid for now re-binding!!
1134 */
1135 for (i = 0; i < stmt->param_count; i++) {
1136 /*
1137 We may have the last reference, then call zval_ptr_dtor() or we may leak memory.
1138 Switching from bind_one_parameter to bind_parameters may result in zv being NULL
1139 */
1140 zval_ptr_dtor(&stmt->param_bind[i].zv);
1141 }
1142 if (stmt->param_bind != param_bind) {
1143 s->m->free_parameter_bind(s, stmt->param_bind);
1144 }
1145 }
1146
1147 stmt->param_bind = param_bind;
1148 for (i = 0; i < stmt->param_count; i++) {
1149 /* The client will use stmt_send_long_data */
1150 DBG_INF_FMT("%u is of type %u", i, stmt->param_bind[i].type);
1151 /* Prevent from freeing */
1152 /* Don't update is_ref, or we will leak during conversion */
1153 Z_TRY_ADDREF(stmt->param_bind[i].zv);
1154 stmt->param_bind[i].flags = 0;
1155 if (stmt->param_bind[i].type == MYSQL_TYPE_LONG_BLOB) {
1156 stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1157 }
1158 }
1159 stmt->send_types_to_server = 1;
1160 } else if (param_bind && param_bind != stmt->param_bind) {
1161 s->m->free_parameter_bind(s, param_bind);
1162 }
1163 DBG_INF("PASS");
1164 DBG_RETURN(PASS);
1165 }
1166 /* }}} */
1167
1168
1169 /* {{{ mysqlnd_stmt::bind_one_parameter */
1170 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_parameter)1171 MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter)(MYSQLND_STMT * const s, unsigned int param_no,
1172 zval * const zv, zend_uchar type)
1173 {
1174 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1175 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1176
1177 DBG_ENTER("mysqlnd_stmt::bind_one_parameter");
1178 if (!stmt || !conn) {
1179 DBG_RETURN(FAIL);
1180 }
1181 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_no=%u param_count=%u type=%u", stmt->stmt_id, param_no, stmt->param_count, type);
1182
1183 if (stmt->state < MYSQLND_STMT_PREPARED) {
1184 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1185 DBG_ERR("not prepared");
1186 DBG_RETURN(FAIL);
1187 }
1188
1189 if (param_no >= stmt->param_count) {
1190 SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1191 DBG_ERR("invalid param_no");
1192 DBG_RETURN(FAIL);
1193 }
1194 SET_EMPTY_ERROR(stmt->error_info);
1195 SET_EMPTY_ERROR(conn->error_info);
1196
1197 if (stmt->param_count) {
1198 if (!stmt->param_bind) {
1199 stmt->param_bind = mnd_ecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND));
1200 if (!stmt->param_bind) {
1201 DBG_RETURN(FAIL);
1202 }
1203 }
1204
1205 /* Prevent from freeing */
1206 /* Don't update is_ref, or we will leak during conversion */
1207 Z_TRY_ADDREF_P(zv);
1208 DBG_INF("Binding");
1209 /* Release what we had, if we had */
1210 zval_ptr_dtor(&stmt->param_bind[param_no].zv);
1211 if (type == MYSQL_TYPE_LONG_BLOB) {
1212 /* The client will use stmt_send_long_data */
1213 stmt->param_bind[param_no].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1214 }
1215 ZVAL_COPY_VALUE(&stmt->param_bind[param_no].zv, zv);
1216 stmt->param_bind[param_no].type = type;
1217
1218 stmt->send_types_to_server = 1;
1219 }
1220 DBG_INF("PASS");
1221 DBG_RETURN(PASS);
1222 }
1223 /* }}} */
1224
1225
1226 /* {{{ mysqlnd_stmt::refresh_bind_param */
1227 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,refresh_bind_param)1228 MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param)(MYSQLND_STMT * const s)
1229 {
1230 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1231 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1232
1233 DBG_ENTER("mysqlnd_stmt::refresh_bind_param");
1234 if (!stmt || !conn) {
1235 DBG_RETURN(FAIL);
1236 }
1237 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_count=%u", stmt->stmt_id, stmt->param_count);
1238
1239 if (stmt->state < MYSQLND_STMT_PREPARED) {
1240 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1241 DBG_ERR("not prepared");
1242 DBG_RETURN(FAIL);
1243 }
1244
1245 SET_EMPTY_ERROR(stmt->error_info);
1246 SET_EMPTY_ERROR(conn->error_info);
1247
1248 if (stmt->param_count) {
1249 stmt->send_types_to_server = 1;
1250 }
1251 DBG_RETURN(PASS);
1252 }
1253 /* }}} */
1254
1255
1256 /* {{{ mysqlnd_stmt::bind_result */
1257 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_result)1258 MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const s,
1259 MYSQLND_RESULT_BIND * const result_bind)
1260 {
1261 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1262 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1263
1264 DBG_ENTER("mysqlnd_stmt::bind_result");
1265 if (!stmt || !conn) {
1266 DBG_RETURN(FAIL);
1267 }
1268 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " field_count=%u", stmt->stmt_id, stmt->field_count);
1269
1270 if (stmt->state < MYSQLND_STMT_PREPARED) {
1271 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1272 if (result_bind) {
1273 s->m->free_result_bind(s, result_bind);
1274 }
1275 DBG_ERR("not prepared");
1276 DBG_RETURN(FAIL);
1277 }
1278
1279 SET_EMPTY_ERROR(stmt->error_info);
1280 SET_EMPTY_ERROR(conn->error_info);
1281
1282 if (stmt->field_count) {
1283 unsigned int i = 0;
1284
1285 if (!result_bind) {
1286 DBG_ERR("no result bind passed");
1287 DBG_RETURN(FAIL);
1288 }
1289
1290 mysqlnd_stmt_separate_result_bind(s);
1291 stmt->result_bind = result_bind;
1292 for (i = 0; i < stmt->field_count; i++) {
1293 /* Prevent from freeing */
1294 Z_TRY_ADDREF(stmt->result_bind[i].zv);
1295
1296 DBG_INF_FMT("ref of %p = %u", &stmt->result_bind[i].zv,
1297 Z_REFCOUNTED(stmt->result_bind[i].zv)? Z_REFCOUNT(stmt->result_bind[i].zv) : 0);
1298 /*
1299 Don't update is_ref !!! it's not our job
1300 Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1301 will fail.
1302 */
1303 stmt->result_bind[i].bound = TRUE;
1304 }
1305 } else if (result_bind) {
1306 s->m->free_result_bind(s, result_bind);
1307 }
1308 DBG_INF("PASS");
1309 DBG_RETURN(PASS);
1310 }
1311 /* }}} */
1312
1313
1314 /* {{{ mysqlnd_stmt::bind_result */
1315 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_result)1316 MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned int param_no)
1317 {
1318 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1319 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1320
1321 DBG_ENTER("mysqlnd_stmt::bind_one_result");
1322 if (!stmt || !conn) {
1323 DBG_RETURN(FAIL);
1324 }
1325 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " field_count=%u", stmt->stmt_id, stmt->field_count);
1326
1327 if (stmt->state < MYSQLND_STMT_PREPARED) {
1328 SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1329 DBG_ERR("not prepared");
1330 DBG_RETURN(FAIL);
1331 }
1332
1333 if (param_no >= stmt->field_count) {
1334 SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1335 DBG_ERR("invalid param_no");
1336 DBG_RETURN(FAIL);
1337 }
1338
1339 SET_EMPTY_ERROR(stmt->error_info);
1340 SET_EMPTY_ERROR(conn->error_info);
1341
1342 if (stmt->field_count) {
1343 if (!stmt->result_bind) {
1344 stmt->result_bind = mnd_ecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND));
1345 }
1346 if (stmt->result_bind[param_no].bound) {
1347 zval_ptr_dtor(&stmt->result_bind[param_no].zv);
1348 }
1349 ZVAL_NULL(&stmt->result_bind[param_no].zv);
1350 stmt->result_bind[param_no].bound = TRUE;
1351 }
1352 DBG_INF("PASS");
1353 DBG_RETURN(PASS);
1354 }
1355 /* }}} */
1356
1357
1358 /* {{{ mysqlnd_stmt::insert_id */
1359 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,insert_id)1360 MYSQLND_METHOD(mysqlnd_stmt, insert_id)(const MYSQLND_STMT * const s)
1361 {
1362 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1363 return stmt? UPSERT_STATUS_GET_LAST_INSERT_ID(stmt->upsert_status) : 0;
1364 }
1365 /* }}} */
1366
1367
1368 /* {{{ mysqlnd_stmt::affected_rows */
1369 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,affected_rows)1370 MYSQLND_METHOD(mysqlnd_stmt, affected_rows)(const MYSQLND_STMT * const s)
1371 {
1372 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1373 return stmt? UPSERT_STATUS_GET_AFFECTED_ROWS(stmt->upsert_status) : 0;
1374 }
1375 /* }}} */
1376
1377
1378 /* {{{ mysqlnd_stmt::num_rows */
1379 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,num_rows)1380 MYSQLND_METHOD(mysqlnd_stmt, num_rows)(const MYSQLND_STMT * const s)
1381 {
1382 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1383 return stmt && stmt->result? mysqlnd_num_rows(stmt->result):0;
1384 }
1385 /* }}} */
1386
1387
1388 /* {{{ mysqlnd_stmt::warning_count */
1389 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,warning_count)1390 MYSQLND_METHOD(mysqlnd_stmt, warning_count)(const MYSQLND_STMT * const s)
1391 {
1392 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1393 return stmt? UPSERT_STATUS_GET_WARNINGS(stmt->upsert_status) : 0;
1394 }
1395 /* }}} */
1396
1397
1398 /* {{{ mysqlnd_stmt::server_status */
1399 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,server_status)1400 MYSQLND_METHOD(mysqlnd_stmt, server_status)(const MYSQLND_STMT * const s)
1401 {
1402 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1403 return stmt? UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) : 0;
1404 }
1405 /* }}} */
1406
1407
1408 /* {{{ mysqlnd_stmt::field_count */
1409 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,field_count)1410 MYSQLND_METHOD(mysqlnd_stmt, field_count)(const MYSQLND_STMT * const s)
1411 {
1412 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1413 return stmt? stmt->field_count : 0;
1414 }
1415 /* }}} */
1416
1417
1418 /* {{{ mysqlnd_stmt::param_count */
1419 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,param_count)1420 MYSQLND_METHOD(mysqlnd_stmt, param_count)(const MYSQLND_STMT * const s)
1421 {
1422 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1423 return stmt? stmt->param_count : 0;
1424 }
1425 /* }}} */
1426
1427
1428 /* {{{ mysqlnd_stmt::errno */
1429 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,errno)1430 MYSQLND_METHOD(mysqlnd_stmt, errno)(const MYSQLND_STMT * const s)
1431 {
1432 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1433 return stmt? stmt->error_info->error_no : 0;
1434 }
1435 /* }}} */
1436
1437
1438 /* {{{ mysqlnd_stmt::error */
1439 static const char *
MYSQLND_METHOD(mysqlnd_stmt,error)1440 MYSQLND_METHOD(mysqlnd_stmt, error)(const MYSQLND_STMT * const s)
1441 {
1442 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1443 return stmt? stmt->error_info->error : 0;
1444 }
1445 /* }}} */
1446
1447
1448 /* {{{ mysqlnd_stmt::sqlstate */
1449 static const char *
MYSQLND_METHOD(mysqlnd_stmt,sqlstate)1450 MYSQLND_METHOD(mysqlnd_stmt, sqlstate)(const MYSQLND_STMT * const s)
1451 {
1452 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1453 return stmt && stmt->error_info->sqlstate[0] ? stmt->error_info->sqlstate:MYSQLND_SQLSTATE_NULL;
1454 }
1455 /* }}} */
1456
1457
1458 /* {{{ mysqlnd_stmt::data_seek */
1459 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,data_seek)1460 MYSQLND_METHOD(mysqlnd_stmt, data_seek)(const MYSQLND_STMT * const s, uint64_t row)
1461 {
1462 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1463 return stmt && stmt->result? stmt->result->m.seek_data(stmt->result, row) : FAIL;
1464 }
1465 /* }}} */
1466
1467
1468 /* {{{ mysqlnd_stmt::result_metadata */
1469 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,result_metadata)1470 MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s)
1471 {
1472 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1473 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1474 MYSQLND_RES * result_meta = NULL;
1475
1476 DBG_ENTER("mysqlnd_stmt::result_metadata");
1477 if (!stmt || ! conn) {
1478 DBG_RETURN(NULL);
1479 }
1480 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " field_count=%u", stmt->stmt_id, stmt->field_count);
1481
1482 if (!stmt->field_count || !stmt->result || !stmt->result->meta) {
1483 DBG_INF("NULL");
1484 DBG_RETURN(NULL);
1485 }
1486
1487 /*
1488 TODO: This implementation is kind of a hack,
1489 find a better way to do it. In different functions I have put
1490 fuses to check for result->m.fetch_row() being NULL. This should
1491 be handled in a better way.
1492 */
1493 do {
1494 result_meta = conn->m->result_init(stmt->field_count);
1495 if (!result_meta) {
1496 break;
1497 }
1498 result_meta->type = MYSQLND_RES_NORMAL;
1499 result_meta->unbuf = mysqlnd_result_unbuffered_init(result_meta, stmt->field_count, stmt);
1500 if (!result_meta->unbuf) {
1501 break;
1502 }
1503 result_meta->unbuf->eof_reached = TRUE;
1504 result_meta->meta = stmt->result->meta->m->clone_metadata(result_meta, stmt->result->meta);
1505 if (!result_meta->meta) {
1506 break;
1507 }
1508
1509 DBG_INF_FMT("result_meta=%p", result_meta);
1510 DBG_RETURN(result_meta);
1511 } while (0);
1512
1513 SET_OOM_ERROR(conn->error_info);
1514 if (result_meta) {
1515 result_meta->m.free_result(result_meta, TRUE);
1516 }
1517 DBG_RETURN(NULL);
1518 }
1519 /* }}} */
1520
1521
1522 /* {{{ mysqlnd_stmt::attr_set */
1523 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_set)1524 MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const s,
1525 enum mysqlnd_stmt_attr attr_type,
1526 const void * const value)
1527 {
1528 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1529 DBG_ENTER("mysqlnd_stmt::attr_set");
1530 if (!stmt) {
1531 DBG_RETURN(FAIL);
1532 }
1533 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " attr_type=%u", stmt->stmt_id, attr_type);
1534
1535 switch (attr_type) {
1536 case STMT_ATTR_UPDATE_MAX_LENGTH:{
1537 zend_uchar bval = *(zend_uchar *) value;
1538 /*
1539 XXX : libmysql uses my_bool, but mysqli uses ulong as storage on the stack
1540 and mysqlnd won't be used out of the scope of PHP -> use ulong.
1541 */
1542 stmt->update_max_length = bval? TRUE:FALSE;
1543 break;
1544 }
1545 case STMT_ATTR_CURSOR_TYPE: {
1546 unsigned long ival = *(unsigned long *) value;
1547 if (ival > (unsigned long) CURSOR_TYPE_READ_ONLY) {
1548 SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1549 DBG_INF("FAIL");
1550 DBG_RETURN(FAIL);
1551 }
1552 stmt->flags = ival;
1553 break;
1554 }
1555 default:
1556 SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1557 DBG_RETURN(FAIL);
1558 }
1559 DBG_INF("PASS");
1560 DBG_RETURN(PASS);
1561 }
1562 /* }}} */
1563
1564
1565 /* {{{ mysqlnd_stmt::attr_get */
1566 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_get)1567 MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s,
1568 enum mysqlnd_stmt_attr attr_type,
1569 void * const value)
1570 {
1571 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1572 DBG_ENTER("mysqlnd_stmt::attr_get");
1573 if (!stmt) {
1574 DBG_RETURN(FAIL);
1575 }
1576 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " attr_type=%u", stmt->stmt_id, attr_type);
1577
1578 switch (attr_type) {
1579 case STMT_ATTR_UPDATE_MAX_LENGTH:
1580 *(bool *) value = stmt->update_max_length;
1581 DBG_INF_FMT("value=%d", *(bool *) value);
1582 break;
1583 case STMT_ATTR_CURSOR_TYPE:
1584 *(unsigned long *) value = stmt->flags;
1585 DBG_INF_FMT("value=%lu", *(unsigned long *) value);
1586 break;
1587 default:
1588 DBG_RETURN(FAIL);
1589 }
1590 DBG_RETURN(PASS);
1591 }
1592 /* }}} */
1593
1594
1595 /* free_result() doesn't actually free stmt->result but only the buffers */
1596 /* {{{ mysqlnd_stmt::free_result */
1597 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,free_result)1598 MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const s)
1599 {
1600 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1601 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1602
1603 DBG_ENTER("mysqlnd_stmt::free_result");
1604 if (!stmt || !conn) {
1605 DBG_RETURN(FAIL);
1606 }
1607 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
1608
1609 if (!stmt->result) {
1610 DBG_INF("no result");
1611 DBG_RETURN(PASS);
1612 }
1613
1614 /*
1615 If right after execute() we have to call the appropriate
1616 use_result() or store_result() and clean.
1617 */
1618 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1619 DBG_INF("fetching result set header");
1620 /* Do implicit use_result and then flush the result */
1621 stmt->default_rset_handler = s->m->use_result;
1622 stmt->default_rset_handler(s);
1623 }
1624
1625 if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
1626 DBG_INF("skipping result");
1627 /* Flush if anything is left and unbuffered set */
1628 stmt->result->m.skip_result(stmt->result);
1629 /*
1630 Separate the bound variables, which point to the result set, then
1631 destroy the set.
1632 */
1633 mysqlnd_stmt_separate_result_bind(s);
1634
1635 /* Now we can destroy the result set */
1636 stmt->result->m.free_result_buffers(stmt->result);
1637 }
1638
1639 if (stmt->state > MYSQLND_STMT_PREPARED) {
1640 /* As the buffers have been freed, we should go back to PREPARED */
1641 stmt->state = MYSQLND_STMT_PREPARED;
1642 }
1643
1644 DBG_RETURN(PASS);
1645 }
1646 /* }}} */
1647
1648
1649 /* {{{ mysqlnd_stmt_separate_result_bind */
1650 static void
mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s)1651 mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s)
1652 {
1653 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1654 unsigned int i;
1655
1656 DBG_ENTER("mysqlnd_stmt_separate_result_bind");
1657 if (!stmt) {
1658 DBG_VOID_RETURN;
1659 }
1660 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " result_bind=%p field_count=%u", stmt->stmt_id, stmt->result_bind, stmt->field_count);
1661
1662 if (!stmt->result_bind) {
1663 DBG_VOID_RETURN;
1664 }
1665
1666 /*
1667 Because only the bound variables can point to our internal buffers, then
1668 separate or free only them. Free is possible because the user could have
1669 lost reference.
1670 */
1671 for (i = 0; i < stmt->field_count; i++) {
1672 /* Let's try with no cache */
1673 if (stmt->result_bind[i].bound == TRUE) {
1674 DBG_INF_FMT("%u has refcount=%u", i, Z_REFCOUNTED(stmt->result_bind[i].zv)? Z_REFCOUNT(stmt->result_bind[i].zv) : 0);
1675 zval_ptr_dtor(&stmt->result_bind[i].zv);
1676 }
1677 }
1678
1679 s->m->free_result_bind(s, stmt->result_bind);
1680 stmt->result_bind = NULL;
1681
1682 DBG_VOID_RETURN;
1683 }
1684 /* }}} */
1685
1686
1687 /* {{{ mysqlnd_stmt::free_stmt_result */
1688 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_result)1689 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)(MYSQLND_STMT * const s)
1690 {
1691 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1692 DBG_ENTER("mysqlnd_stmt::free_stmt_result");
1693 if (!stmt) {
1694 DBG_VOID_RETURN;
1695 }
1696
1697 /*
1698 First separate the bound variables, which point to the result set, then
1699 destroy the set.
1700 */
1701 mysqlnd_stmt_separate_result_bind(s);
1702 /* Not every statement has a result set attached */
1703 if (stmt->result) {
1704 stmt->result->m.free_result(stmt->result, /* implicit */ TRUE);
1705 stmt->result = NULL;
1706 }
1707 zend_llist_clean(&stmt->error_info->error_list);
1708
1709 DBG_VOID_RETURN;
1710 }
1711 /* }}} */
1712
1713
1714 /* {{{ mysqlnd_stmt::free_stmt_content */
1715 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_content)1716 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content)(MYSQLND_STMT * const s)
1717 {
1718 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1719 DBG_ENTER("mysqlnd_stmt::free_stmt_content");
1720 if (!stmt) {
1721 DBG_VOID_RETURN;
1722 }
1723 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_bind=%p param_count=%u", stmt->stmt_id, stmt->param_bind, stmt->param_count);
1724
1725 /* Destroy the input bind */
1726 if (stmt->param_bind) {
1727 unsigned int i;
1728 /*
1729 Because only the bound variables can point to our internal buffers, then
1730 separate or free only them. Free is possible because the user could have
1731 lost reference.
1732 */
1733 for (i = 0; i < stmt->param_count; i++) {
1734 /*
1735 If bind_one_parameter was used, but not everything was
1736 bound and nothing was fetched, then some `zv` could be NULL
1737 */
1738 zval_ptr_dtor(&stmt->param_bind[i].zv);
1739 }
1740 s->m->free_parameter_bind(s, stmt->param_bind);
1741 stmt->param_bind = NULL;
1742 }
1743
1744 s->m->free_stmt_result(s);
1745 DBG_VOID_RETURN;
1746 }
1747 /* }}} */
1748
1749
1750 /* {{{ mysqlnd_stmt::close_on_server */
1751 static enum_func_status
MYSQLND_METHOD_PRIVATE(mysqlnd_stmt,close_on_server)1752 MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, close_on_server)(MYSQLND_STMT * const s, bool implicit)
1753 {
1754 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1755 MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1756 enum_mysqlnd_collected_stats statistic = STAT_LAST;
1757
1758 DBG_ENTER("mysqlnd_stmt::close_on_server");
1759 if (!stmt || !conn) {
1760 DBG_RETURN(FAIL);
1761 }
1762 DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
1763
1764 SET_EMPTY_ERROR(stmt->error_info);
1765 SET_EMPTY_ERROR(conn->error_info);
1766
1767 /*
1768 If the user decided to close the statement right after execute()
1769 We have to call the appropriate use_result() or store_result() and
1770 clean.
1771 */
1772 do {
1773 if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1774 DBG_INF("fetching result set header");
1775 stmt->default_rset_handler(s);
1776 stmt->state = MYSQLND_STMT_USER_FETCHING;
1777 }
1778
1779 /* unbuffered set not fetched to the end ? Clean the line */
1780 if (stmt->result) {
1781 DBG_INF("skipping result");
1782 stmt->result->m.skip_result(stmt->result);
1783 }
1784 } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
1785 /*
1786 After this point we are allowed to free the result set,
1787 as we have cleaned the line
1788 */
1789 if (stmt->stmt_id) {
1790 MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_FREE_RESULT_IMPLICIT:
1791 STAT_FREE_RESULT_EXPLICIT);
1792
1793 if (GET_CONNECTION_STATE(&conn->state) == CONN_READY) {
1794 enum_func_status ret = FAIL;
1795 const size_t stmt_id = stmt->stmt_id;
1796
1797 ret = conn->command->stmt_close(conn, stmt_id);
1798 if (ret == FAIL) {
1799 COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
1800 DBG_RETURN(FAIL);
1801 }
1802 }
1803 }
1804 switch (stmt->execute_count) {
1805 case 0:
1806 statistic = STAT_PS_PREPARED_NEVER_EXECUTED;
1807 break;
1808 case 1:
1809 statistic = STAT_PS_PREPARED_ONCE_USED;
1810 break;
1811 default:
1812 break;
1813 }
1814 if (statistic != STAT_LAST) {
1815 MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
1816 }
1817
1818 if (stmt->execute_cmd_buffer.buffer) {
1819 mnd_efree(stmt->execute_cmd_buffer.buffer);
1820 stmt->execute_cmd_buffer.buffer = NULL;
1821 }
1822
1823 s->m->free_stmt_content(s);
1824
1825 if (conn) {
1826 conn->m->free_reference(conn);
1827 stmt->conn = NULL;
1828 }
1829
1830 DBG_RETURN(PASS);
1831 }
1832 /* }}} */
1833
1834 /* {{{ mysqlnd_stmt::dtor */
1835 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,dtor)1836 MYSQLND_METHOD(mysqlnd_stmt, dtor)(MYSQLND_STMT * const s, bool implicit)
1837 {
1838 MYSQLND_STMT_DATA * stmt = (s != NULL) ? s->data:NULL;
1839 enum_func_status ret = FAIL;
1840
1841 DBG_ENTER("mysqlnd_stmt::dtor");
1842 if (stmt) {
1843 DBG_INF_FMT("stmt=%p", stmt);
1844
1845 MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_STMT_CLOSE_IMPLICIT:
1846 STAT_STMT_CLOSE_EXPLICIT);
1847
1848 ret = s->m->close_on_server(s, implicit);
1849 mnd_efree(stmt);
1850 }
1851 mnd_efree(s);
1852
1853 DBG_INF(ret == PASS? "PASS":"FAIL");
1854 DBG_RETURN(ret);
1855 }
1856 /* }}} */
1857
1858
1859 /* {{{ mysqlnd_stmt::alloc_param_bind */
1860 static MYSQLND_PARAM_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_param_bind)1861 MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind)(MYSQLND_STMT * const s)
1862 {
1863 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1864 DBG_ENTER("mysqlnd_stmt::alloc_param_bind");
1865 if (!stmt) {
1866 DBG_RETURN(NULL);
1867 }
1868 DBG_RETURN(mnd_ecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND)));
1869 }
1870 /* }}} */
1871
1872
1873 /* {{{ mysqlnd_stmt::alloc_result_bind */
1874 static MYSQLND_RESULT_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_result_bind)1875 MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind)(MYSQLND_STMT * const s)
1876 {
1877 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1878 DBG_ENTER("mysqlnd_stmt::alloc_result_bind");
1879 if (!stmt) {
1880 DBG_RETURN(NULL);
1881 }
1882 DBG_RETURN(mnd_ecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND)));
1883 }
1884 /* }}} */
1885
1886
1887 /* {{{ param_bind::free_parameter_bind */
1888 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_parameter_bind)1889 MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * param_bind)
1890 {
1891 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1892 if (stmt) {
1893 mnd_efree(param_bind);
1894 }
1895 }
1896 /* }}} */
1897
1898
1899 /* {{{ mysqlnd_stmt::free_result_bind */
1900 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_result_bind)1901 MYSQLND_METHOD(mysqlnd_stmt, free_result_bind)(MYSQLND_STMT * const s, MYSQLND_RESULT_BIND * result_bind)
1902 {
1903 MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1904 if (stmt) {
1905 mnd_efree(result_bind);
1906 }
1907 }
1908 /* }}} */
1909
1910
1911
1912 MYSQLND_CLASS_METHODS_START(mysqlnd_stmt)
1913 MYSQLND_METHOD(mysqlnd_stmt, prepare),
1914 MYSQLND_METHOD(mysqlnd_stmt, send_execute),
1915 MYSQLND_METHOD(mysqlnd_stmt, execute),
1916 MYSQLND_METHOD(mysqlnd_stmt, use_result),
1917 MYSQLND_METHOD(mysqlnd_stmt, store_result),
1918 MYSQLND_METHOD(mysqlnd_stmt, get_result),
1919 MYSQLND_METHOD(mysqlnd_stmt, more_results),
1920 MYSQLND_METHOD(mysqlnd_stmt, next_result),
1921 MYSQLND_METHOD(mysqlnd_stmt, free_result),
1922 MYSQLND_METHOD(mysqlnd_stmt, data_seek),
1923 MYSQLND_METHOD(mysqlnd_stmt, reset),
1924 MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, close_on_server),
1925 MYSQLND_METHOD(mysqlnd_stmt, dtor),
1926
1927 MYSQLND_METHOD(mysqlnd_stmt, fetch),
1928
1929 MYSQLND_METHOD(mysqlnd_stmt, bind_parameters),
1930 MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter),
1931 MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param),
1932 MYSQLND_METHOD(mysqlnd_stmt, bind_result),
1933 MYSQLND_METHOD(mysqlnd_stmt, bind_one_result),
1934 MYSQLND_METHOD(mysqlnd_stmt, send_long_data),
1935
1936 MYSQLND_METHOD(mysqlnd_stmt, result_metadata),
1937
1938 MYSQLND_METHOD(mysqlnd_stmt, insert_id),
1939 MYSQLND_METHOD(mysqlnd_stmt, affected_rows),
1940 MYSQLND_METHOD(mysqlnd_stmt, num_rows),
1941
1942 MYSQLND_METHOD(mysqlnd_stmt, param_count),
1943 MYSQLND_METHOD(mysqlnd_stmt, field_count),
1944 MYSQLND_METHOD(mysqlnd_stmt, warning_count),
1945
1946 MYSQLND_METHOD(mysqlnd_stmt, errno),
1947 MYSQLND_METHOD(mysqlnd_stmt, error),
1948 MYSQLND_METHOD(mysqlnd_stmt, sqlstate),
1949
1950 MYSQLND_METHOD(mysqlnd_stmt, attr_get),
1951 MYSQLND_METHOD(mysqlnd_stmt, attr_set),
1952
1953
1954 MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind),
1955 MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind),
1956 MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind),
1957 MYSQLND_METHOD(mysqlnd_stmt, free_result_bind),
1958 MYSQLND_METHOD(mysqlnd_stmt, server_status),
1959 mysqlnd_stmt_execute_generate_request,
1960 mysqlnd_stmt_execute_parse_response,
1961 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content),
1962 MYSQLND_METHOD(mysqlnd_stmt, flush),
1963 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)
1964 MYSQLND_CLASS_METHODS_END;
1965
1966
1967 /* {{{ _mysqlnd_init_ps_subsystem */
_mysqlnd_init_ps_subsystem(void)1968 void _mysqlnd_init_ps_subsystem(void)
1969 {
1970 mysqlnd_stmt_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_stmt));
1971 _mysqlnd_init_ps_fetch_subsystem();
1972 }
1973 /* }}} */
1974