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