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