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