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