xref: /PHP-8.1/ext/mysqlnd/mysqlnd_ps.c (revision ced5581e)
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 		*/
656 		stmt->result->m.free_result_buffers(stmt->result);
657 
658 		stmt->state = MYSQLND_STMT_PREPARED;
659 	} else if (stmt->state < MYSQLND_STMT_PREPARED) {
660 		/* Only initted - error */
661 		SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
662 		DBG_INF("FAIL");
663 		DBG_RETURN(FAIL);
664 	}
665 
666 	if (stmt->param_count) {
667 		unsigned int i, not_bound = 0;
668 		if (!stmt->param_bind) {
669 			SET_CLIENT_ERROR(stmt->error_info, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, "No data supplied for parameters in prepared statement");
670 			DBG_INF("FAIL");
671 			DBG_RETURN(FAIL);
672 		}
673 		for (i = 0; i < stmt->param_count; i++) {
674 			if (Z_ISUNDEF(stmt->param_bind[i].zv)) {
675 				not_bound++;
676 			}
677 		}
678 		if (not_bound) {
679 			char * msg;
680 			mnd_sprintf(&msg, 0, "No data supplied for %u parameter%s in prepared statement",
681 						not_bound, not_bound>1 ?"s":"");
682 			SET_CLIENT_ERROR(stmt->error_info, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, msg);
683 			if (msg) {
684 				mnd_sprintf_free(msg);
685 			}
686 			DBG_INF("FAIL");
687 			DBG_RETURN(FAIL);
688 		}
689 	}
690 	ret = s->m->generate_execute_request(s, &request, &request_len, &free_request);
691 	if (ret == PASS) {
692 		const MYSQLND_CSTRING payload = {(const char*) request, request_len};
693 
694 		ret = conn->command->stmt_execute(conn, payload);
695 	} else {
696 		SET_CLIENT_ERROR(stmt->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Couldn't generate the request. Possibly OOM.");
697 	}
698 
699 	if (free_request) {
700 		mnd_efree(request);
701 	}
702 
703 	if (ret == FAIL) {
704 		COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
705 		DBG_INF("FAIL");
706 		DBG_RETURN(FAIL);
707 	}
708 	stmt->execute_count++;
709 
710 	DBG_RETURN(PASS);
711 }
712 /* }}} */
713 
714 
715 /* {{{ mysqlnd_stmt::use_result */
716 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,use_result)717 MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s)
718 {
719 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
720 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
721 	MYSQLND_RES * result;
722 
723 	DBG_ENTER("mysqlnd_stmt::use_result");
724 	if (!stmt || !conn || !stmt->result) {
725 		DBG_RETURN(NULL);
726 	}
727 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
728 
729 	if (!stmt->field_count || !mysqlnd_stmt_check_state(stmt)) {
730 		SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
731 		DBG_ERR("command out of sync");
732 		DBG_RETURN(NULL);
733 	}
734 
735 	SET_EMPTY_ERROR(stmt->error_info);
736 
737 	MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_PS_UNBUFFERED_SETS);
738 	result = stmt->result;
739 
740 	result->m.use_result(stmt->result, stmt);
741 	if (stmt->cursor_exists) {
742 		result->unbuf->m.fetch_row = mysqlnd_fetch_stmt_row_cursor;
743 	}
744 	stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
745 
746 	DBG_INF_FMT("%p", result);
747 	DBG_RETURN(result);
748 }
749 /* }}} */
750 
751 
752 /* {{{ mysqlnd_fetch_row_cursor */
753 enum_func_status
mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result,zval ** row_ptr,const unsigned int flags,bool * fetched_anything)754 mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, zval **row_ptr, const unsigned int flags, bool * fetched_anything)
755 {
756 	enum_func_status ret;
757 	MYSQLND_STMT_DATA * stmt = result->unbuf->stmt;
758 	MYSQLND_CONN_DATA * conn = stmt->conn;
759 	MYSQLND_PACKET_ROW * row_packet;
760 	void *checkpoint;
761 
762 	DBG_ENTER("mysqlnd_fetch_stmt_row_cursor");
763 
764 	if (!stmt || !stmt->conn || !result || !result->conn || !result->unbuf) {
765 		DBG_ERR("no statement");
766 		DBG_RETURN(FAIL);
767 	}
768 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " flags=%u", stmt->stmt_id, flags);
769 
770 	if (stmt->state < MYSQLND_STMT_USER_FETCHING) {
771 		/* Only initted - error */
772 		SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
773 		DBG_ERR("command out of sync");
774 		DBG_RETURN(FAIL);
775 	}
776 	if (!(row_packet = result->unbuf->row_packet)) {
777 		DBG_RETURN(FAIL);
778 	}
779 
780 	SET_EMPTY_ERROR(stmt->error_info);
781 	SET_EMPTY_ERROR(conn->error_info);
782 
783 	/* for now fetch only one row */
784 	if (mysqlnd_stmt_send_cursor_fetch_command(stmt, 1) == FAIL) {
785 		DBG_RETURN(FAIL);
786 	}
787 
788 	checkpoint = result->memory_pool->checkpoint;
789 	mysqlnd_mempool_save_state(result->memory_pool);
790 
791 	UPSERT_STATUS_RESET(stmt->upsert_status);
792 	if (PASS == (ret = PACKET_READ(conn, row_packet)) && !row_packet->eof) {
793 		if (row_ptr) {
794 			result->unbuf->last_row_buffer = row_packet->row_buffer;
795 			row_packet->row_buffer.ptr = NULL;
796 			*row_ptr = result->row_data;
797 
798 			if (PASS != result->unbuf->m.row_decoder(&result->unbuf->last_row_buffer,
799 									  result->row_data,
800 									  row_packet->field_count,
801 									  row_packet->fields_metadata,
802 									  conn->options->int_and_float_native,
803 									  conn->stats))
804 			{
805 				mysqlnd_mempool_restore_state(result->memory_pool);
806 				result->memory_pool->checkpoint = checkpoint;
807 				DBG_RETURN(FAIL);
808 			}
809 		} else {
810 			DBG_INF("skipping extraction");
811 			row_packet->row_buffer.ptr = NULL;
812 		}
813 		/* We asked for one row, the next one should be EOF, eat it */
814 		ret = PACKET_READ(conn, row_packet);
815 		MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR);
816 
817 		result->unbuf->row_count++;
818 		*fetched_anything = TRUE;
819 	} else {
820 		*fetched_anything = FALSE;
821 		UPSERT_STATUS_SET_WARNINGS(stmt->upsert_status, row_packet->warning_count);
822 		UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
823 
824 		UPSERT_STATUS_SET_SERVER_STATUS(stmt->upsert_status, row_packet->server_status);
825 		UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
826 
827 		result->unbuf->eof_reached = row_packet->eof;
828 	}
829 	UPSERT_STATUS_SET_WARNINGS(stmt->upsert_status, row_packet->warning_count);
830 	UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
831 
832 	UPSERT_STATUS_SET_SERVER_STATUS(stmt->upsert_status, row_packet->server_status);
833 	UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
834 
835 	mysqlnd_mempool_restore_state(result->memory_pool);
836 	result->memory_pool->checkpoint = checkpoint;
837 
838 	DBG_INF_FMT("ret=%s fetched=%u server_status=%u warnings=%u eof=%u",
839 				ret == PASS? "PASS":"FAIL", *fetched_anything,
840 				row_packet->server_status, row_packet->warning_count,
841 				result->unbuf->eof_reached);
842 	DBG_RETURN(ret);
843 }
844 /* }}} */
845 
846 
847 /* {{{ mysqlnd_stmt::fetch */
848 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,fetch)849 MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const s, bool * const fetched_anything)
850 {
851 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
852 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
853 	enum_func_status ret;
854 	DBG_ENTER("mysqlnd_stmt::fetch");
855 	if (!stmt || !stmt->conn) {
856 		DBG_RETURN(FAIL);
857 	}
858 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
859 
860 	if (!stmt->result || stmt->state < MYSQLND_STMT_WAITING_USE_OR_STORE) {
861 		SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
862 		DBG_ERR("command out of sync");
863 		DBG_RETURN(FAIL);
864 	} else if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
865 		/* Execute only once. We have to free the previous contents of user's bound vars */
866 
867 		stmt->default_rset_handler(s);
868 	}
869 	stmt->state = MYSQLND_STMT_USER_FETCHING;
870 
871 	SET_EMPTY_ERROR(stmt->error_info);
872 	SET_EMPTY_ERROR(conn->error_info);
873 
874 	if (stmt->result_bind) {
875 		zval *row_data;
876 		ret = stmt->result->m.fetch_row(stmt->result, &row_data, 0, fetched_anything);
877 		if (ret == PASS && *fetched_anything) {
878 			unsigned field_count = stmt->result->field_count;
879 			for (unsigned i = 0; i < field_count; i++) {
880 				zval *resultzv = &stmt->result_bind[i].zv;
881 				if (stmt->result_bind[i].bound == TRUE) {
882 					DBG_INF_FMT("i=%u type=%u", i, Z_TYPE(row_data[i]));
883 					ZEND_TRY_ASSIGN_VALUE_EX(resultzv, &row_data[i], 0);
884 				} else {
885 					zval_ptr_dtor_nogc(&row_data[i]);
886 				}
887 			}
888 		}
889 	} else {
890 		ret = stmt->result->m.fetch_row(stmt->result, NULL, 0, fetched_anything);
891 	}
892 	DBG_RETURN(ret);
893 }
894 /* }}} */
895 
896 
897 /* {{{ mysqlnd_stmt::reset */
898 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,reset)899 MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const s)
900 {
901 	enum_func_status ret = PASS;
902 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
903 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
904 
905 	DBG_ENTER("mysqlnd_stmt::reset");
906 	if (!stmt || !conn) {
907 		DBG_RETURN(FAIL);
908 	}
909 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
910 
911 	SET_EMPTY_ERROR(stmt->error_info);
912 	SET_EMPTY_ERROR(conn->error_info);
913 
914 	if (stmt->stmt_id) {
915 		MYSQLND_CONN_DATA * conn = stmt->conn;
916 		if (stmt->param_bind) {
917 			unsigned int i;
918 			DBG_INF("resetting long data");
919 			/* Reset Long Data */
920 			for (i = 0; i < stmt->param_count; i++) {
921 				if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
922 					stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
923 				}
924 			}
925 		}
926 
927 		s->m->flush(s);
928 
929 		/*
930 		  Don't free now, let the result be usable. When the stmt will again be
931 		  executed then the result set will be cleaned, the bound variables will
932 		  be separated before that.
933 		*/
934 
935 		if (GET_CONNECTION_STATE(&conn->state) == CONN_READY) {
936 			size_t stmt_id = stmt->stmt_id;
937 
938 			ret = stmt->conn->command->stmt_reset(stmt->conn, stmt_id);
939 			if (ret == FAIL) {
940 				COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
941 			}
942 		}
943 		*stmt->upsert_status = *conn->upsert_status;
944 	}
945 	DBG_INF(ret == PASS? "PASS":"FAIL");
946 	DBG_RETURN(ret);
947 }
948 /* }}} */
949 
950 
951 /* {{{ mysqlnd_stmt::flush */
952 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,flush)953 MYSQLND_METHOD(mysqlnd_stmt, flush)(MYSQLND_STMT * const s)
954 {
955 	enum_func_status ret = PASS;
956 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
957 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
958 
959 	DBG_ENTER("mysqlnd_stmt::flush");
960 	if (!stmt || !conn) {
961 		DBG_RETURN(FAIL);
962 	}
963 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
964 
965 	if (stmt->stmt_id) {
966 		/*
967 		  If the user decided to close the statement right after execute()
968 		  We have to call the appropriate use_result() or store_result() and
969 		  clean.
970 		*/
971 		do {
972 			if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
973 				DBG_INF("fetching result set header");
974 				stmt->default_rset_handler(s);
975 				stmt->state = MYSQLND_STMT_USER_FETCHING;
976 			}
977 
978 			if (stmt->result) {
979 				DBG_INF("skipping result");
980 				stmt->result->m.skip_result(stmt->result);
981 			}
982 		} while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
983 	}
984 	DBG_INF(ret == PASS? "PASS":"FAIL");
985 	DBG_RETURN(ret);
986 }
987 /* }}} */
988 
989 
990 /* {{{ mysqlnd_stmt::send_long_data */
991 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,send_long_data)992 MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const s, unsigned int param_no,
993 							 				 const char * const data, zend_ulong data_length)
994 {
995 	enum_func_status ret = FAIL;
996 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
997 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
998 	zend_uchar * cmd_buf;
999 
1000 	DBG_ENTER("mysqlnd_stmt::send_long_data");
1001 	if (!stmt || !conn) {
1002 		DBG_RETURN(FAIL);
1003 	}
1004 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_no=%u data_len=" ZEND_ULONG_FMT, stmt->stmt_id, param_no, data_length);
1005 
1006 	SET_EMPTY_ERROR(stmt->error_info);
1007 	SET_EMPTY_ERROR(conn->error_info);
1008 
1009 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1010 		SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1011 		DBG_ERR("not prepared");
1012 		DBG_RETURN(FAIL);
1013 	}
1014 	if (!stmt->param_bind) {
1015 		SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1016 		DBG_ERR("command out of sync");
1017 		DBG_RETURN(FAIL);
1018 	}
1019 	if (param_no >= stmt->param_count) {
1020 		SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1021 		DBG_ERR("invalid param_no");
1022 		DBG_RETURN(FAIL);
1023 	}
1024 	if (stmt->param_bind[param_no].type != MYSQL_TYPE_LONG_BLOB) {
1025 		SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_BUFFER_USE, UNKNOWN_SQLSTATE, mysqlnd_not_bound_as_blob);
1026 		DBG_ERR("param_no is not of a blob type");
1027 		DBG_RETURN(FAIL);
1028 	}
1029 
1030 	if (GET_CONNECTION_STATE(&conn->state) == CONN_READY) {
1031 		const size_t packet_len = MYSQLND_STMT_ID_LENGTH + 2 + data_length;
1032 		cmd_buf = mnd_emalloc(packet_len);
1033 		if (cmd_buf) {
1034 			stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED;
1035 
1036 			int4store(cmd_buf, stmt->stmt_id);
1037 			int2store(cmd_buf + MYSQLND_STMT_ID_LENGTH, param_no);
1038 			memcpy(cmd_buf + MYSQLND_STMT_ID_LENGTH + 2, data, data_length);
1039 
1040 			/* COM_STMT_SEND_LONG_DATA doesn't acknowledge with an OK packet */
1041 			{
1042 				const MYSQLND_CSTRING payload = {(const char *) cmd_buf, packet_len};
1043 
1044 				ret = conn->command->stmt_send_long_data(conn, payload);
1045 				if (ret == FAIL) {
1046 					COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
1047 				}
1048 			}
1049 
1050 			mnd_efree(cmd_buf);
1051 		} else {
1052 			ret = FAIL;
1053 			SET_OOM_ERROR(stmt->error_info);
1054 			SET_OOM_ERROR(conn->error_info);
1055 		}
1056 		/*
1057 		  Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not
1058 		  sent response packets. According to documentation the only way to get an error
1059 		  is to have out-of-memory on the server-side. However, that's not true, as if
1060 		  max_allowed_packet_size is smaller than the chunk being sent to the server, the
1061 		  latter will complain with an error message. However, normally we don't expect
1062 		  an error message, thus we continue. When sending the next command, which expects
1063 		  response we will read the unexpected data and error message will look weird.
1064 		  Therefore we do non-blocking read to clean the line, if there is a need.
1065 		  Nevertheless, there is a built-in protection when sending a command packet, that
1066 		  checks if the line is clear - useful for debug purposes and to be switched off
1067 		  in release builds.
1068 
1069 		  Maybe we can make it automatic by checking what's the value of
1070 		  max_allowed_packet_size on the server and resending the data.
1071 		*/
1072 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
1073 #if defined(HAVE_USLEEP) && !defined(PHP_WIN32)
1074 		usleep(120000);
1075 #endif
1076 		if ((packet_len = conn->protocol_frame_codec->m.consume_uneaten_data(conn->protocol_frame_codec, COM_STMT_SEND_LONG_DATA))) {
1077 			php_error_docref(NULL, E_WARNING, "There was an error "
1078 							 "while sending long data. Probably max_allowed_packet_size "
1079 							 "is smaller than the data. You have to increase it or send "
1080 							 "smaller chunks of data. Answer was %zu bytes long.", packet_len);
1081 			SET_CLIENT_ERROR(stmt->error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE,
1082 							"Server responded to COM_STMT_SEND_LONG_DATA.");
1083 			ret = FAIL;
1084 		}
1085 #endif
1086 	}
1087 
1088 	DBG_INF(ret == PASS? "PASS":"FAIL");
1089 	DBG_RETURN(ret);
1090 }
1091 /* }}} */
1092 
1093 
1094 /* {{{ mysqlnd_stmt::bind_parameters */
1095 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_parameters)1096 MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * const param_bind)
1097 {
1098 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1099 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1100 
1101 	DBG_ENTER("mysqlnd_stmt::bind_param");
1102 	if (!stmt || !conn) {
1103 		DBG_RETURN(FAIL);
1104 	}
1105 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_count=%u", stmt->stmt_id, stmt->param_count);
1106 
1107 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1108 		SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1109 		DBG_ERR("not prepared");
1110 		if (param_bind) {
1111 			s->m->free_parameter_bind(s, param_bind);
1112 		}
1113 		DBG_RETURN(FAIL);
1114 	}
1115 
1116 	SET_EMPTY_ERROR(stmt->error_info);
1117 	SET_EMPTY_ERROR(conn->error_info);
1118 
1119 	if (stmt->param_count) {
1120 		unsigned int i = 0;
1121 
1122 		if (!param_bind) {
1123 			SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, "Re-binding (still) not supported");
1124 			DBG_ERR("Re-binding (still) not supported");
1125 			DBG_RETURN(FAIL);
1126 		} else if (stmt->param_bind) {
1127 			DBG_INF("Binding");
1128 			/*
1129 			  There is already result bound.
1130 			  Forbid for now re-binding!!
1131 			*/
1132 			for (i = 0; i < stmt->param_count; i++) {
1133 				/*
1134 				  We may have the last reference, then call zval_ptr_dtor() or we may leak memory.
1135 				  Switching from bind_one_parameter to bind_parameters may result in zv being NULL
1136 				*/
1137 				zval_ptr_dtor(&stmt->param_bind[i].zv);
1138 			}
1139 			if (stmt->param_bind != param_bind) {
1140 				s->m->free_parameter_bind(s, stmt->param_bind);
1141 			}
1142 		}
1143 
1144 		stmt->param_bind = param_bind;
1145 		for (i = 0; i < stmt->param_count; i++) {
1146 			/* The client will use stmt_send_long_data */
1147 			DBG_INF_FMT("%u is of type %u", i, stmt->param_bind[i].type);
1148 			/* Prevent from freeing */
1149 			/* Don't update is_ref, or we will leak during conversion */
1150 			Z_TRY_ADDREF(stmt->param_bind[i].zv);
1151 			stmt->param_bind[i].flags = 0;
1152 			if (stmt->param_bind[i].type == MYSQL_TYPE_LONG_BLOB) {
1153 				stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1154 			}
1155 		}
1156 		stmt->send_types_to_server = 1;
1157 	} else if (param_bind && param_bind != stmt->param_bind) {
1158 		s->m->free_parameter_bind(s, param_bind);
1159 	}
1160 	DBG_INF("PASS");
1161 	DBG_RETURN(PASS);
1162 }
1163 /* }}} */
1164 
1165 
1166 /* {{{ mysqlnd_stmt::bind_one_parameter */
1167 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_parameter)1168 MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter)(MYSQLND_STMT * const s, unsigned int param_no,
1169 												 zval * const zv, zend_uchar type)
1170 {
1171 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1172 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1173 
1174 	DBG_ENTER("mysqlnd_stmt::bind_one_parameter");
1175 	if (!stmt || !conn) {
1176 		DBG_RETURN(FAIL);
1177 	}
1178 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_no=%u param_count=%u type=%u", stmt->stmt_id, param_no, stmt->param_count, type);
1179 
1180 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1181 		SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1182 		DBG_ERR("not prepared");
1183 		DBG_RETURN(FAIL);
1184 	}
1185 
1186 	if (param_no >= stmt->param_count) {
1187 		SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1188 		DBG_ERR("invalid param_no");
1189 		DBG_RETURN(FAIL);
1190 	}
1191 	SET_EMPTY_ERROR(stmt->error_info);
1192 	SET_EMPTY_ERROR(conn->error_info);
1193 
1194 	if (stmt->param_count) {
1195 		if (!stmt->param_bind) {
1196 			stmt->param_bind = mnd_ecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND));
1197 			if (!stmt->param_bind) {
1198 				DBG_RETURN(FAIL);
1199 			}
1200 		}
1201 
1202 		/* Prevent from freeing */
1203 		/* Don't update is_ref, or we will leak during conversion */
1204 		Z_TRY_ADDREF_P(zv);
1205 		DBG_INF("Binding");
1206 		/* Release what we had, if we had */
1207 		zval_ptr_dtor(&stmt->param_bind[param_no].zv);
1208 		if (type == MYSQL_TYPE_LONG_BLOB) {
1209 			/* The client will use stmt_send_long_data */
1210 			stmt->param_bind[param_no].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1211 		}
1212 		ZVAL_COPY_VALUE(&stmt->param_bind[param_no].zv, zv);
1213 		stmt->param_bind[param_no].type = type;
1214 
1215 		stmt->send_types_to_server = 1;
1216 	}
1217 	DBG_INF("PASS");
1218 	DBG_RETURN(PASS);
1219 }
1220 /* }}} */
1221 
1222 
1223 /* {{{ mysqlnd_stmt::refresh_bind_param */
1224 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,refresh_bind_param)1225 MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param)(MYSQLND_STMT * const s)
1226 {
1227 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1228 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1229 
1230 	DBG_ENTER("mysqlnd_stmt::refresh_bind_param");
1231 	if (!stmt || !conn) {
1232 		DBG_RETURN(FAIL);
1233 	}
1234 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_count=%u", stmt->stmt_id, stmt->param_count);
1235 
1236 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1237 		SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1238 		DBG_ERR("not prepared");
1239 		DBG_RETURN(FAIL);
1240 	}
1241 
1242 	SET_EMPTY_ERROR(stmt->error_info);
1243 	SET_EMPTY_ERROR(conn->error_info);
1244 
1245 	if (stmt->param_count) {
1246 		stmt->send_types_to_server = 1;
1247 	}
1248 	DBG_RETURN(PASS);
1249 }
1250 /* }}} */
1251 
1252 
1253 /* {{{ mysqlnd_stmt::bind_result */
1254 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_result)1255 MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const s,
1256 										  MYSQLND_RESULT_BIND * const result_bind)
1257 {
1258 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1259 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1260 
1261 	DBG_ENTER("mysqlnd_stmt::bind_result");
1262 	if (!stmt || !conn) {
1263 		DBG_RETURN(FAIL);
1264 	}
1265 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " field_count=%u", stmt->stmt_id, stmt->field_count);
1266 
1267 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1268 		SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1269 		if (result_bind) {
1270 			s->m->free_result_bind(s, result_bind);
1271 		}
1272 		DBG_ERR("not prepared");
1273 		DBG_RETURN(FAIL);
1274 	}
1275 
1276 	SET_EMPTY_ERROR(stmt->error_info);
1277 	SET_EMPTY_ERROR(conn->error_info);
1278 
1279 	if (stmt->field_count) {
1280 		unsigned int i = 0;
1281 
1282 		if (!result_bind) {
1283 			DBG_ERR("no result bind passed");
1284 			DBG_RETURN(FAIL);
1285 		}
1286 
1287 		mysqlnd_stmt_separate_result_bind(s);
1288 		stmt->result_bind = result_bind;
1289 		for (i = 0; i < stmt->field_count; i++) {
1290 			/* Prevent from freeing */
1291 			Z_TRY_ADDREF(stmt->result_bind[i].zv);
1292 
1293 			DBG_INF_FMT("ref of %p = %u", &stmt->result_bind[i].zv,
1294 					Z_REFCOUNTED(stmt->result_bind[i].zv)? Z_REFCOUNT(stmt->result_bind[i].zv) : 0);
1295 			/*
1296 			  Don't update is_ref !!! it's not our job
1297 			  Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1298 			  will fail.
1299 			*/
1300 			stmt->result_bind[i].bound = TRUE;
1301 		}
1302 	} else if (result_bind) {
1303 		s->m->free_result_bind(s, result_bind);
1304 	}
1305 	DBG_INF("PASS");
1306 	DBG_RETURN(PASS);
1307 }
1308 /* }}} */
1309 
1310 
1311 /* {{{ mysqlnd_stmt::bind_result */
1312 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_result)1313 MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned int param_no)
1314 {
1315 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1316 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1317 
1318 	DBG_ENTER("mysqlnd_stmt::bind_result");
1319 	if (!stmt || !conn) {
1320 		DBG_RETURN(FAIL);
1321 	}
1322 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " field_count=%u", stmt->stmt_id, stmt->field_count);
1323 
1324 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1325 		SET_CLIENT_ERROR(stmt->error_info, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1326 		DBG_ERR("not prepared");
1327 		DBG_RETURN(FAIL);
1328 	}
1329 
1330 	if (param_no >= stmt->field_count) {
1331 		SET_CLIENT_ERROR(stmt->error_info, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1332 		DBG_ERR("invalid param_no");
1333 		DBG_RETURN(FAIL);
1334 	}
1335 
1336 	SET_EMPTY_ERROR(stmt->error_info);
1337 	SET_EMPTY_ERROR(conn->error_info);
1338 
1339 	if (stmt->field_count) {
1340 		if (!stmt->result_bind) {
1341 			stmt->result_bind = mnd_ecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND));
1342 		}
1343 		if (stmt->result_bind[param_no].bound) {
1344 			zval_ptr_dtor(&stmt->result_bind[param_no].zv);
1345 		}
1346 		ZVAL_NULL(&stmt->result_bind[param_no].zv);
1347 		stmt->result_bind[param_no].bound = TRUE;
1348 	}
1349 	DBG_INF("PASS");
1350 	DBG_RETURN(PASS);
1351 }
1352 /* }}} */
1353 
1354 
1355 /* {{{ mysqlnd_stmt::insert_id */
1356 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,insert_id)1357 MYSQLND_METHOD(mysqlnd_stmt, insert_id)(const MYSQLND_STMT * const s)
1358 {
1359 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1360 	return stmt? UPSERT_STATUS_GET_LAST_INSERT_ID(stmt->upsert_status) : 0;
1361 }
1362 /* }}} */
1363 
1364 
1365 /* {{{ mysqlnd_stmt::affected_rows */
1366 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,affected_rows)1367 MYSQLND_METHOD(mysqlnd_stmt, affected_rows)(const MYSQLND_STMT * const s)
1368 {
1369 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1370 	return stmt? UPSERT_STATUS_GET_AFFECTED_ROWS(stmt->upsert_status) : 0;
1371 }
1372 /* }}} */
1373 
1374 
1375 /* {{{ mysqlnd_stmt::num_rows */
1376 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,num_rows)1377 MYSQLND_METHOD(mysqlnd_stmt, num_rows)(const MYSQLND_STMT * const s)
1378 {
1379 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1380 	return stmt && stmt->result? mysqlnd_num_rows(stmt->result):0;
1381 }
1382 /* }}} */
1383 
1384 
1385 /* {{{ mysqlnd_stmt::warning_count */
1386 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,warning_count)1387 MYSQLND_METHOD(mysqlnd_stmt, warning_count)(const MYSQLND_STMT * const s)
1388 {
1389 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1390 	return stmt? UPSERT_STATUS_GET_WARNINGS(stmt->upsert_status) : 0;
1391 }
1392 /* }}} */
1393 
1394 
1395 /* {{{ mysqlnd_stmt::server_status */
1396 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,server_status)1397 MYSQLND_METHOD(mysqlnd_stmt, server_status)(const MYSQLND_STMT * const s)
1398 {
1399 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1400 	return stmt? UPSERT_STATUS_GET_SERVER_STATUS(stmt->upsert_status) : 0;
1401 }
1402 /* }}} */
1403 
1404 
1405 /* {{{ mysqlnd_stmt::field_count */
1406 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,field_count)1407 MYSQLND_METHOD(mysqlnd_stmt, field_count)(const MYSQLND_STMT * const s)
1408 {
1409 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1410 	return stmt? stmt->field_count : 0;
1411 }
1412 /* }}} */
1413 
1414 
1415 /* {{{ mysqlnd_stmt::param_count */
1416 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,param_count)1417 MYSQLND_METHOD(mysqlnd_stmt, param_count)(const MYSQLND_STMT * const s)
1418 {
1419 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1420 	return stmt? stmt->param_count : 0;
1421 }
1422 /* }}} */
1423 
1424 
1425 /* {{{ mysqlnd_stmt::errno */
1426 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,errno)1427 MYSQLND_METHOD(mysqlnd_stmt, errno)(const MYSQLND_STMT * const s)
1428 {
1429 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1430 	return stmt? stmt->error_info->error_no : 0;
1431 }
1432 /* }}} */
1433 
1434 
1435 /* {{{ mysqlnd_stmt::error */
1436 static const char *
MYSQLND_METHOD(mysqlnd_stmt,error)1437 MYSQLND_METHOD(mysqlnd_stmt, error)(const MYSQLND_STMT * const s)
1438 {
1439 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1440 	return stmt? stmt->error_info->error : 0;
1441 }
1442 /* }}} */
1443 
1444 
1445 /* {{{ mysqlnd_stmt::sqlstate */
1446 static const char *
MYSQLND_METHOD(mysqlnd_stmt,sqlstate)1447 MYSQLND_METHOD(mysqlnd_stmt, sqlstate)(const MYSQLND_STMT * const s)
1448 {
1449 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1450 	return stmt && stmt->error_info->sqlstate[0] ? stmt->error_info->sqlstate:MYSQLND_SQLSTATE_NULL;
1451 }
1452 /* }}} */
1453 
1454 
1455 /* {{{ mysqlnd_stmt::data_seek */
1456 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,data_seek)1457 MYSQLND_METHOD(mysqlnd_stmt, data_seek)(const MYSQLND_STMT * const s, uint64_t row)
1458 {
1459 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1460 	return stmt && stmt->result? stmt->result->m.seek_data(stmt->result, row) : FAIL;
1461 }
1462 /* }}} */
1463 
1464 
1465 /* {{{ mysqlnd_stmt::result_metadata */
1466 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,result_metadata)1467 MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s)
1468 {
1469 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1470 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1471 	MYSQLND_RES * result_meta = NULL;
1472 
1473 	DBG_ENTER("mysqlnd_stmt::result_metadata");
1474 	if (!stmt || ! conn) {
1475 		DBG_RETURN(NULL);
1476 	}
1477 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " field_count=%u", stmt->stmt_id, stmt->field_count);
1478 
1479 	if (!stmt->field_count || !stmt->result || !stmt->result->meta) {
1480 		DBG_INF("NULL");
1481 		DBG_RETURN(NULL);
1482 	}
1483 
1484 	/*
1485 	  TODO: This implementation is kind of a hack,
1486 			find a better way to do it. In different functions I have put
1487 			fuses to check for result->m.fetch_row() being NULL. This should
1488 			be handled in a better way.
1489 	*/
1490 	do {
1491 		result_meta = conn->m->result_init(stmt->field_count);
1492 		if (!result_meta) {
1493 			break;
1494 		}
1495 		result_meta->type = MYSQLND_RES_NORMAL;
1496 		result_meta->unbuf = mysqlnd_result_unbuffered_init(result_meta, stmt->field_count, stmt);
1497 		if (!result_meta->unbuf) {
1498 			break;
1499 		}
1500 		result_meta->unbuf->eof_reached = TRUE;
1501 		result_meta->meta = stmt->result->meta->m->clone_metadata(result_meta, stmt->result->meta);
1502 		if (!result_meta->meta) {
1503 			break;
1504 		}
1505 
1506 		DBG_INF_FMT("result_meta=%p", result_meta);
1507 		DBG_RETURN(result_meta);
1508 	} while (0);
1509 
1510 	SET_OOM_ERROR(conn->error_info);
1511 	if (result_meta) {
1512 		result_meta->m.free_result(result_meta, TRUE);
1513 	}
1514 	DBG_RETURN(NULL);
1515 }
1516 /* }}} */
1517 
1518 
1519 /* {{{ mysqlnd_stmt::attr_set */
1520 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_set)1521 MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const s,
1522 									   enum mysqlnd_stmt_attr attr_type,
1523 									   const void * const value)
1524 {
1525 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1526 	DBG_ENTER("mysqlnd_stmt::attr_set");
1527 	if (!stmt) {
1528 		DBG_RETURN(FAIL);
1529 	}
1530 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " attr_type=%u", stmt->stmt_id, attr_type);
1531 
1532 	switch (attr_type) {
1533 		case STMT_ATTR_UPDATE_MAX_LENGTH:{
1534 			zend_uchar bval = *(zend_uchar *) value;
1535 			/*
1536 			  XXX : libmysql uses my_bool, but mysqli uses ulong as storage on the stack
1537 			  and mysqlnd won't be used out of the scope of PHP -> use ulong.
1538 			*/
1539 			stmt->update_max_length = bval? TRUE:FALSE;
1540 			break;
1541 		}
1542 		case STMT_ATTR_CURSOR_TYPE: {
1543 			unsigned long ival = *(unsigned long *) value;
1544 			if (ival > (unsigned long) CURSOR_TYPE_READ_ONLY) {
1545 				SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1546 				DBG_INF("FAIL");
1547 				DBG_RETURN(FAIL);
1548 			}
1549 			stmt->flags = ival;
1550 			break;
1551 		}
1552 		case STMT_ATTR_PREFETCH_ROWS: {
1553 			unsigned long ival = *(unsigned long *) value;
1554 			if (ival == 0) {
1555 				ival = MYSQLND_DEFAULT_PREFETCH_ROWS;
1556 			} else if (ival > 1) {
1557 				SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1558 				DBG_INF("FAIL");
1559 				DBG_RETURN(FAIL);
1560 			}
1561 			stmt->prefetch_rows = ival;
1562 			break;
1563 		}
1564 		default:
1565 			SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1566 			DBG_RETURN(FAIL);
1567 	}
1568 	DBG_INF("PASS");
1569 	DBG_RETURN(PASS);
1570 }
1571 /* }}} */
1572 
1573 
1574 /* {{{ mysqlnd_stmt::attr_get */
1575 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_get)1576 MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s,
1577 									   enum mysqlnd_stmt_attr attr_type,
1578 									   void * const value)
1579 {
1580 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1581 	DBG_ENTER("mysqlnd_stmt::attr_set");
1582 	if (!stmt) {
1583 		DBG_RETURN(FAIL);
1584 	}
1585 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " attr_type=%u", stmt->stmt_id, attr_type);
1586 
1587 	switch (attr_type) {
1588 		case STMT_ATTR_UPDATE_MAX_LENGTH:
1589 			*(bool *) value = stmt->update_max_length;
1590 			DBG_INF_FMT("value=%d", *(bool *) value);
1591 			break;
1592 		case STMT_ATTR_CURSOR_TYPE:
1593 			*(unsigned long *) value = stmt->flags;
1594 			DBG_INF_FMT("value=%lu", *(unsigned long *) value);
1595 			break;
1596 		case STMT_ATTR_PREFETCH_ROWS:
1597 			*(unsigned long *) value = stmt->prefetch_rows;
1598 			DBG_INF_FMT("value=%lu", *(unsigned long *) value);
1599 			break;
1600 		default:
1601 			DBG_RETURN(FAIL);
1602 	}
1603 	DBG_RETURN(PASS);
1604 }
1605 /* }}} */
1606 
1607 
1608 /* free_result() doesn't actually free stmt->result but only the buffers */
1609 /* {{{ mysqlnd_stmt::free_result */
1610 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,free_result)1611 MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const s)
1612 {
1613 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1614 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1615 
1616 	DBG_ENTER("mysqlnd_stmt::free_result");
1617 	if (!stmt || !conn) {
1618 		DBG_RETURN(FAIL);
1619 	}
1620 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
1621 
1622 	if (!stmt->result) {
1623 		DBG_INF("no result");
1624 		DBG_RETURN(PASS);
1625 	}
1626 
1627 	/*
1628 	  If right after execute() we have to call the appropriate
1629 	  use_result() or store_result() and clean.
1630 	*/
1631 	if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1632 		DBG_INF("fetching result set header");
1633 		/* Do implicit use_result and then flush the result */
1634 		stmt->default_rset_handler = s->m->use_result;
1635 		stmt->default_rset_handler(s);
1636 	}
1637 
1638 	if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
1639 		DBG_INF("skipping result");
1640 		/* Flush if anything is left and unbuffered set */
1641 		stmt->result->m.skip_result(stmt->result);
1642 		/*
1643 		  Separate the bound variables, which point to the result set, then
1644 		  destroy the set.
1645 		*/
1646 		mysqlnd_stmt_separate_result_bind(s);
1647 
1648 		/* Now we can destroy the result set */
1649 		stmt->result->m.free_result_buffers(stmt->result);
1650 	}
1651 
1652 	if (stmt->state > MYSQLND_STMT_PREPARED) {
1653 		/* As the buffers have been freed, we should go back to PREPARED */
1654 		stmt->state = MYSQLND_STMT_PREPARED;
1655 	}
1656 
1657 	DBG_RETURN(PASS);
1658 }
1659 /* }}} */
1660 
1661 
1662 /* {{{ mysqlnd_stmt_separate_result_bind */
1663 static void
mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s)1664 mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s)
1665 {
1666 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1667 	unsigned int i;
1668 
1669 	DBG_ENTER("mysqlnd_stmt_separate_result_bind");
1670 	if (!stmt) {
1671 		DBG_VOID_RETURN;
1672 	}
1673 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " result_bind=%p field_count=%u", stmt->stmt_id, stmt->result_bind, stmt->field_count);
1674 
1675 	if (!stmt->result_bind) {
1676 		DBG_VOID_RETURN;
1677 	}
1678 
1679 	/*
1680 	  Because only the bound variables can point to our internal buffers, then
1681 	  separate or free only them. Free is possible because the user could have
1682 	  lost reference.
1683 	*/
1684 	for (i = 0; i < stmt->field_count; i++) {
1685 		/* Let's try with no cache */
1686 		if (stmt->result_bind[i].bound == TRUE) {
1687 			DBG_INF_FMT("%u has refcount=%u", i, Z_REFCOUNTED(stmt->result_bind[i].zv)? Z_REFCOUNT(stmt->result_bind[i].zv) : 0);
1688 			zval_ptr_dtor(&stmt->result_bind[i].zv);
1689 		}
1690 	}
1691 
1692 	s->m->free_result_bind(s, stmt->result_bind);
1693 	stmt->result_bind = NULL;
1694 
1695 	DBG_VOID_RETURN;
1696 }
1697 /* }}} */
1698 
1699 
1700 /* {{{ mysqlnd_stmt::free_stmt_result */
1701 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_result)1702 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)(MYSQLND_STMT * const s)
1703 {
1704 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1705 	DBG_ENTER("mysqlnd_stmt::free_stmt_result");
1706 	if (!stmt) {
1707 		DBG_VOID_RETURN;
1708 	}
1709 
1710 	/*
1711 	  First separate the bound variables, which point to the result set, then
1712 	  destroy the set.
1713 	*/
1714 	mysqlnd_stmt_separate_result_bind(s);
1715 	/* Not every statement has a result set attached */
1716 	if (stmt->result) {
1717 		stmt->result->m.free_result(stmt->result, /* implicit */ TRUE);
1718 		stmt->result = NULL;
1719 	}
1720 	zend_llist_clean(&stmt->error_info->error_list);
1721 
1722 	DBG_VOID_RETURN;
1723 }
1724 /* }}} */
1725 
1726 
1727 /* {{{ mysqlnd_stmt::free_stmt_content */
1728 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_content)1729 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content)(MYSQLND_STMT * const s)
1730 {
1731 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1732 	DBG_ENTER("mysqlnd_stmt::free_stmt_content");
1733 	if (!stmt) {
1734 		DBG_VOID_RETURN;
1735 	}
1736 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT " param_bind=%p param_count=%u", stmt->stmt_id, stmt->param_bind, stmt->param_count);
1737 
1738 	/* Destroy the input bind */
1739 	if (stmt->param_bind) {
1740 		unsigned int i;
1741 		/*
1742 		  Because only the bound variables can point to our internal buffers, then
1743 		  separate or free only them. Free is possible because the user could have
1744 		  lost reference.
1745 		*/
1746 		for (i = 0; i < stmt->param_count; i++) {
1747 			/*
1748 			  If bind_one_parameter was used, but not everything was
1749 			  bound and nothing was fetched, then some `zv` could be NULL
1750 			*/
1751 			zval_ptr_dtor(&stmt->param_bind[i].zv);
1752 		}
1753 		s->m->free_parameter_bind(s, stmt->param_bind);
1754 		stmt->param_bind = NULL;
1755 	}
1756 
1757 	s->m->free_stmt_result(s);
1758 	DBG_VOID_RETURN;
1759 }
1760 /* }}} */
1761 
1762 
1763 /* {{{ mysqlnd_stmt::close_on_server */
1764 static enum_func_status
MYSQLND_METHOD_PRIVATE(mysqlnd_stmt,close_on_server)1765 MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, close_on_server)(MYSQLND_STMT * const s, bool implicit)
1766 {
1767 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1768 	MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
1769 	enum_mysqlnd_collected_stats statistic = STAT_LAST;
1770 
1771 	DBG_ENTER("mysqlnd_stmt::close_on_server");
1772 	if (!stmt || !conn) {
1773 		DBG_RETURN(FAIL);
1774 	}
1775 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt->stmt_id);
1776 
1777 	SET_EMPTY_ERROR(stmt->error_info);
1778 	SET_EMPTY_ERROR(conn->error_info);
1779 
1780 	/*
1781 	  If the user decided to close the statement right after execute()
1782 	  We have to call the appropriate use_result() or store_result() and
1783 	  clean.
1784 	*/
1785 	do {
1786 		if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1787 			DBG_INF("fetching result set header");
1788 			stmt->default_rset_handler(s);
1789 			stmt->state = MYSQLND_STMT_USER_FETCHING;
1790 		}
1791 
1792 		/* unbuffered set not fetched to the end ? Clean the line */
1793 		if (stmt->result) {
1794 			DBG_INF("skipping result");
1795 			stmt->result->m.skip_result(stmt->result);
1796 		}
1797 	} while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
1798 	/*
1799 	  After this point we are allowed to free the result set,
1800 	  as we have cleaned the line
1801 	*/
1802 	if (stmt->stmt_id) {
1803 		MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE?	STAT_FREE_RESULT_IMPLICIT:
1804 														STAT_FREE_RESULT_EXPLICIT);
1805 
1806 		if (GET_CONNECTION_STATE(&conn->state) == CONN_READY) {
1807 			enum_func_status ret = FAIL;
1808 			const size_t stmt_id = stmt->stmt_id;
1809 
1810 			ret = conn->command->stmt_close(conn, stmt_id);
1811 			if (ret == FAIL) {
1812 				COPY_CLIENT_ERROR(stmt->error_info, *conn->error_info);
1813 				DBG_RETURN(FAIL);
1814 			}
1815 		}
1816 	}
1817 	switch (stmt->execute_count) {
1818 		case 0:
1819 			statistic = STAT_PS_PREPARED_NEVER_EXECUTED;
1820 			break;
1821 		case 1:
1822 			statistic = STAT_PS_PREPARED_ONCE_USED;
1823 			break;
1824 		default:
1825 			break;
1826 	}
1827 	if (statistic != STAT_LAST) {
1828 		MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
1829 	}
1830 
1831 	if (stmt->execute_cmd_buffer.buffer) {
1832 		mnd_efree(stmt->execute_cmd_buffer.buffer);
1833 		stmt->execute_cmd_buffer.buffer = NULL;
1834 	}
1835 
1836 	s->m->free_stmt_content(s);
1837 
1838 	if (conn) {
1839 		conn->m->free_reference(conn);
1840 		stmt->conn = NULL;
1841 	}
1842 
1843 	DBG_RETURN(PASS);
1844 }
1845 /* }}} */
1846 
1847 /* {{{ mysqlnd_stmt::dtor */
1848 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,dtor)1849 MYSQLND_METHOD(mysqlnd_stmt, dtor)(MYSQLND_STMT * const s, bool implicit)
1850 {
1851 	MYSQLND_STMT_DATA * stmt = (s != NULL) ? s->data:NULL;
1852 	enum_func_status ret = FAIL;
1853 
1854 	DBG_ENTER("mysqlnd_stmt::dtor");
1855 	if (stmt) {
1856 		DBG_INF_FMT("stmt=%p", stmt);
1857 
1858 		MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE?	STAT_STMT_CLOSE_IMPLICIT:
1859 														STAT_STMT_CLOSE_EXPLICIT);
1860 
1861 		ret = s->m->close_on_server(s, implicit);
1862 		mnd_efree(stmt);
1863 	}
1864 	mnd_efree(s);
1865 
1866 	DBG_INF(ret == PASS? "PASS":"FAIL");
1867 	DBG_RETURN(ret);
1868 }
1869 /* }}} */
1870 
1871 
1872 /* {{{ mysqlnd_stmt::alloc_param_bind */
1873 static MYSQLND_PARAM_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_param_bind)1874 MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind)(MYSQLND_STMT * const s)
1875 {
1876 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1877 	DBG_ENTER("mysqlnd_stmt::alloc_param_bind");
1878 	if (!stmt) {
1879 		DBG_RETURN(NULL);
1880 	}
1881 	DBG_RETURN(mnd_ecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND)));
1882 }
1883 /* }}} */
1884 
1885 
1886 /* {{{ mysqlnd_stmt::alloc_result_bind */
1887 static MYSQLND_RESULT_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_result_bind)1888 MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind)(MYSQLND_STMT * const s)
1889 {
1890 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1891 	DBG_ENTER("mysqlnd_stmt::alloc_result_bind");
1892 	if (!stmt) {
1893 		DBG_RETURN(NULL);
1894 	}
1895 	DBG_RETURN(mnd_ecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND)));
1896 }
1897 /* }}} */
1898 
1899 
1900 /* {{{ param_bind::free_parameter_bind */
1901 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_parameter_bind)1902 MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * param_bind)
1903 {
1904 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1905 	if (stmt) {
1906 		mnd_efree(param_bind);
1907 	}
1908 }
1909 /* }}} */
1910 
1911 
1912 /* {{{ mysqlnd_stmt::free_result_bind */
1913 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_result_bind)1914 MYSQLND_METHOD(mysqlnd_stmt, free_result_bind)(MYSQLND_STMT * const s, MYSQLND_RESULT_BIND * result_bind)
1915 {
1916 	MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
1917 	if (stmt) {
1918 		mnd_efree(result_bind);
1919 	}
1920 }
1921 /* }}} */
1922 
1923 
1924 
1925 MYSQLND_CLASS_METHODS_START(mysqlnd_stmt)
1926 	MYSQLND_METHOD(mysqlnd_stmt, prepare),
1927 	MYSQLND_METHOD(mysqlnd_stmt, send_execute),
1928 	MYSQLND_METHOD(mysqlnd_stmt, execute),
1929 	MYSQLND_METHOD(mysqlnd_stmt, use_result),
1930 	MYSQLND_METHOD(mysqlnd_stmt, store_result),
1931 	MYSQLND_METHOD(mysqlnd_stmt, get_result),
1932 	MYSQLND_METHOD(mysqlnd_stmt, more_results),
1933 	MYSQLND_METHOD(mysqlnd_stmt, next_result),
1934 	MYSQLND_METHOD(mysqlnd_stmt, free_result),
1935 	MYSQLND_METHOD(mysqlnd_stmt, data_seek),
1936 	MYSQLND_METHOD(mysqlnd_stmt, reset),
1937 	MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, close_on_server),
1938 	MYSQLND_METHOD(mysqlnd_stmt, dtor),
1939 
1940 	MYSQLND_METHOD(mysqlnd_stmt, fetch),
1941 
1942 	MYSQLND_METHOD(mysqlnd_stmt, bind_parameters),
1943 	MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter),
1944 	MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param),
1945 	MYSQLND_METHOD(mysqlnd_stmt, bind_result),
1946 	MYSQLND_METHOD(mysqlnd_stmt, bind_one_result),
1947 	MYSQLND_METHOD(mysqlnd_stmt, send_long_data),
1948 
1949 	MYSQLND_METHOD(mysqlnd_stmt, result_metadata),
1950 
1951 	MYSQLND_METHOD(mysqlnd_stmt, insert_id),
1952 	MYSQLND_METHOD(mysqlnd_stmt, affected_rows),
1953 	MYSQLND_METHOD(mysqlnd_stmt, num_rows),
1954 
1955 	MYSQLND_METHOD(mysqlnd_stmt, param_count),
1956 	MYSQLND_METHOD(mysqlnd_stmt, field_count),
1957 	MYSQLND_METHOD(mysqlnd_stmt, warning_count),
1958 
1959 	MYSQLND_METHOD(mysqlnd_stmt, errno),
1960 	MYSQLND_METHOD(mysqlnd_stmt, error),
1961 	MYSQLND_METHOD(mysqlnd_stmt, sqlstate),
1962 
1963 	MYSQLND_METHOD(mysqlnd_stmt, attr_get),
1964 	MYSQLND_METHOD(mysqlnd_stmt, attr_set),
1965 
1966 
1967 	MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind),
1968 	MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind),
1969 	MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind),
1970 	MYSQLND_METHOD(mysqlnd_stmt, free_result_bind),
1971 	MYSQLND_METHOD(mysqlnd_stmt, server_status),
1972 	mysqlnd_stmt_execute_generate_request,
1973 	mysqlnd_stmt_execute_parse_response,
1974 	MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content),
1975 	MYSQLND_METHOD(mysqlnd_stmt, flush),
1976 	MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)
1977 MYSQLND_CLASS_METHODS_END;
1978 
1979 
1980 /* {{{ _mysqlnd_init_ps_subsystem */
_mysqlnd_init_ps_subsystem()1981 void _mysqlnd_init_ps_subsystem()
1982 {
1983 	mysqlnd_stmt_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_stmt));
1984 	_mysqlnd_init_ps_fetch_subsystem();
1985 }
1986 /* }}} */
1987