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