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