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