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