xref: /PHP-5.6/ext/mysqlnd/mysqlnd_ps.c (revision 49493a2d)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2006-2016 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 static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC);
43 static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC);
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 TSRMLS_DC)
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 TSRMLS_CC));
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC);
132 		mnd_efree(stmt->result);
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 TSRMLS_DC)
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 TSRMLS_CC));
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC))) {
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 TSRMLS_CC);
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 TSRMLS_DC)
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 TSRMLS_CC) & 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 TSRMLS_DC)
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 TSRMLS_CC);
248 	{
249 		enum_func_status ret = s->m->parse_execute_response(s TSRMLS_CC);
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 TSRMLS_DC)258 mysqlnd_stmt_skip_metadata(MYSQLND_STMT * s TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_DC)295 mysqlnd_stmt_read_prepare_response(MYSQLND_STMT * s TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_DC)339 mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT * s TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_CC);
359 				mnd_efree(stmt->result);
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 TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC) ||
420 		FAIL == mysqlnd_stmt_read_prepare_response(s_to_prepare TSRMLS_CC))
421 	{
422 		goto fail;
423 	}
424 
425 	if (stmt_to_prepare->param_count) {
426 		if (FAIL == mysqlnd_stmt_skip_metadata(s_to_prepare TSRMLS_CC) ||
427 			FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare TSRMLS_CC))
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 TSRMLS_CC);
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 TSRMLS_CC);
448 
449 		result->type = MYSQLND_RES_PS_BUF;
450 
451 		if (FAIL == result->m.read_result_metadata(result, stmt_to_prepare->conn TSRMLS_CC) ||
452 			FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare TSRMLS_CC))
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_DC)491 mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s TSRMLS_DC)
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC);
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 TSRMLS_CC);
593 	}
594 #endif
595 
596 	DBG_INF(ret == PASS? "PASS":"FAIL");
597 	DBG_RETURN(ret);
598 }
599 /* }}} */
600 
601 
602 /* {{{ mysqlnd_stmt::execute */
603 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,execute)604 MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const s TSRMLS_DC)
605 {
606 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
607 	enum_func_status ret;
608 	MYSQLND_CONN_DATA * conn;
609 	zend_uchar *request = NULL;
610 	size_t		request_len;
611 	zend_bool	free_request;
612 
613 	DBG_ENTER("mysqlnd_stmt::execute");
614 	if (!stmt || !stmt->conn) {
615 		DBG_RETURN(FAIL);
616 	}
617 	conn = stmt->conn;
618 	DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
619 
620 	SET_ERROR_AFF_ROWS(stmt);
621 	SET_ERROR_AFF_ROWS(stmt->conn);
622 
623 	if (stmt->result && stmt->state >= MYSQLND_STMT_PREPARED && stmt->field_count) {
624 		/*
625 		  We don need to copy the data from the buffers which we will clean.
626 		  Because it has already been copied. See
627 		  #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
628 		*/
629 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
630 		if (stmt->result_bind &&
631 			stmt->result_zvals_separated_once == TRUE &&
632 			stmt->state >= MYSQLND_STMT_USER_FETCHING)
633 		{
634 			/*
635 			  We need to copy the data from the buffers which we will clean.
636 			  The bound variables point to them only if the user has started
637 			  to fetch data (MYSQLND_STMT_USER_FETCHING).
638 			  We need to check 'result_zvals_separated_once' or we will leak
639 			  in the following scenario
640 			  prepare("select 1 from dual");
641 			  execute();
642 			  fetch(); <-- no binding, but that's not a problem
643 			  bind_result();
644 			  execute(); <-- here we will leak because we separate without need
645 			*/
646 			unsigned int i;
647 			for (i = 0; i < stmt->field_count; i++) {
648 				if (stmt->result_bind[i].bound == TRUE) {
649 					zval_copy_ctor(stmt->result_bind[i].zv);
650 				}
651 			}
652 		}
653 #endif
654 
655 		s->m->flush(s TSRMLS_CC);
656 
657 		/*
658 		  Executed, but the user hasn't started to fetch
659 		  This will clean also the metadata, but after the EXECUTE call we will
660 		  have it again.
661 		*/
662 		stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC);
663 
664 		stmt->state = MYSQLND_STMT_PREPARED;
665 	} else if (stmt->state < MYSQLND_STMT_PREPARED) {
666 		/* Only initted - error */
667 		SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
668 						 mysqlnd_out_of_sync);
669 		SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
670 		DBG_INF("FAIL");
671 		DBG_RETURN(FAIL);
672 	}
673 
674 	if (stmt->param_count) {
675 		unsigned int i, not_bound = 0;
676 		if (!stmt->param_bind) {
677 			SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE,
678 							 "No data supplied for parameters in prepared statement");
679 			DBG_INF("FAIL");
680 			DBG_RETURN(FAIL);
681 		}
682 		for (i = 0; i < stmt->param_count; i++) {
683 			if (stmt->param_bind[i].zv == NULL) {
684 				not_bound++;
685 			}
686 		}
687 		if (not_bound) {
688 			char * msg;
689 			mnd_sprintf(&msg, 0, "No data supplied for %u parameter%s in prepared statement",
690 						not_bound, not_bound>1 ?"s":"");
691 			SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, msg);
692 			if (msg) {
693 				mnd_sprintf_free(msg);
694 			}
695 			DBG_INF("FAIL");
696 			DBG_RETURN(FAIL);
697 		}
698 	}
699 	ret = s->m->generate_execute_request(s, &request, &request_len, &free_request TSRMLS_CC);
700 	if (ret == PASS) {
701 		/* support for buffer types should be added here ! */
702 		ret = stmt->conn->m->simple_command(stmt->conn, COM_STMT_EXECUTE, request, request_len,
703 											PROT_LAST /* we will handle the response packet*/,
704 											FALSE, FALSE TSRMLS_CC);
705 	} else {
706 		SET_STMT_ERROR(stmt, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Couldn't generate the request. Possibly OOM.");
707 	}
708 
709 	if (free_request) {
710 		mnd_efree(request);
711 	}
712 
713 	if (ret == FAIL) {
714 		COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info);
715 		DBG_INF("FAIL");
716 		DBG_RETURN(FAIL);
717 	}
718 	stmt->execute_count++;
719 
720 	ret = s->m->parse_execute_response(s TSRMLS_CC);
721 
722 	DBG_INF_FMT("server_status=%u cursor=%u", stmt->upsert_status->server_status, stmt->upsert_status->server_status & SERVER_STATUS_CURSOR_EXISTS);
723 
724 	if (ret == PASS && conn->last_query_type == QUERY_UPSERT && stmt->upsert_status->affected_rows) {
725 		MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_ROWS_AFFECTED_PS, stmt->upsert_status->affected_rows);
726 	}
727 	DBG_RETURN(ret);
728 }
729 /* }}} */
730 
731 
732 /* {{{ mysqlnd_stmt_fetch_row_buffered */
733 enum_func_status
mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)734 mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC)
735 {
736 	MYSQLND_STMT * s = (MYSQLND_STMT *) param;
737 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
738 	const MYSQLND_RES_METADATA * const meta = result->meta;
739 	unsigned int field_count = meta->field_count;
740 
741 	DBG_ENTER("mysqlnd_stmt_fetch_row_buffered");
742 	*fetched_anything = FALSE;
743 	DBG_INF_FMT("stmt=%lu", stmt != NULL ? stmt->stmt_id : 0L);
744 
745 	/* If we haven't read everything */
746 	if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) {
747 		MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
748 		if (set->data_cursor &&
749 			(set->data_cursor - set->data) < (result->stored_data->row_count * field_count))
750 		{
751 			/* The user could have skipped binding - don't crash*/
752 			if (stmt->result_bind) {
753 				unsigned int i;
754 				zval **current_row = set->data_cursor;
755 
756 				if (NULL == current_row[0]) {
757 					uint64_t row_num = (set->data_cursor - set->data) / field_count;
758 					enum_func_status rc = result->stored_data->m.row_decoder(result->stored_data->row_buffers[row_num],
759 													current_row,
760 													meta->field_count,
761 													meta->fields,
762 													result->conn->options->int_and_float_native,
763 													result->conn->stats TSRMLS_CC);
764 					if (PASS != rc) {
765 						DBG_RETURN(FAIL);
766 					}
767 					result->stored_data->initialized_rows++;
768 					if (stmt->update_max_length) {
769 						for (i = 0; i < result->field_count; i++) {
770 							/*
771 							  NULL fields are 0 length, 0 is not more than 0
772 							  String of zero size, definitely can't be the next max_length.
773 							  Thus for NULL and zero-length we are quite efficient.
774 							*/
775 							if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
776 								unsigned long len = Z_STRLEN_P(current_row[i]);
777 								if (meta->fields[i].max_length < len) {
778 									meta->fields[i].max_length = len;
779 								}
780 							}
781 						}
782 					}
783 				}
784 
785 				for (i = 0; i < result->field_count; i++) {
786 					/* Clean what we copied last time */
787 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
788 					if (stmt->result_bind[i].zv) {
789 						zval_dtor(stmt->result_bind[i].zv);
790 					}
791 #endif
792 					/* copy the type */
793 					if (stmt->result_bind[i].bound == TRUE) {
794 						DBG_INF_FMT("i=%u type=%u", i, Z_TYPE_P(current_row[i]));
795 						if (Z_TYPE_P(current_row[i]) != IS_NULL) {
796 							/*
797 							  Copy the value.
798 							  Pre-condition is that the zvals in the result_bind buffer
799 							  have been  ZVAL_NULL()-ed or to another simple type
800 							  (int, double, bool but not string). Because of the reference
801 							  counting the user can't delete the strings the variables point to.
802 							*/
803 
804 							Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]);
805 							stmt->result_bind[i].zv->value = current_row[i]->value;
806 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
807 							zval_copy_ctor(stmt->result_bind[i].zv);
808 #endif
809 						} else {
810 							ZVAL_NULL(stmt->result_bind[i].zv);
811 						}
812 					}
813 				}
814 			}
815 			set->data_cursor += field_count;
816 			*fetched_anything = TRUE;
817 			/* buffered result sets don't have a connection */
818 			MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF);
819 			DBG_INF("row fetched");
820 		} else {
821 			set->data_cursor = NULL;
822 			DBG_INF("no more data");
823 		}
824 	} else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) {
825 		/*TODO*/
826 	}
827 	DBG_INF("PASS");
828 	DBG_RETURN(PASS);
829 }
830 /* }}} */
831 
832 
833 /* {{{ mysqlnd_stmt_fetch_row_unbuffered */
834 enum_func_status
mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)835 mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC)
836 {
837 	enum_func_status ret;
838 	MYSQLND_STMT * s = (MYSQLND_STMT *) param;
839 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
840 	MYSQLND_PACKET_ROW * row_packet;
841 	const MYSQLND_RES_METADATA * const meta = result->meta;
842 
843 	DBG_ENTER("mysqlnd_stmt_fetch_row_unbuffered");
844 
845 	*fetched_anything = FALSE;
846 
847 	if (result->unbuf->eof_reached) {
848 		/* No more rows obviously */
849 		DBG_INF("EOF already reached");
850 		DBG_RETURN(PASS);
851 	}
852 	if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
853 		SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
854 						 UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
855 		DBG_ERR("command out of sync");
856 		DBG_RETURN(FAIL);
857 	}
858 	if (!(row_packet = result->unbuf->row_packet)) {
859 		DBG_RETURN(FAIL);
860 	}
861 
862 	/* Let the row packet fill our buffer and skip additional malloc + memcpy */
863 	row_packet->skip_extraction = stmt && stmt->result_bind? FALSE:TRUE;
864 
865 	/*
866 	  If we skip rows (stmt == NULL || stmt->result_bind == NULL) we have to
867 	  result->unbuf->m.free_last_data() before it. The function returns always true.
868 	*/
869 	if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
870 		unsigned int i, field_count = result->field_count;
871 
872 		if (!row_packet->skip_extraction) {
873 			result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC);
874 
875 			result->unbuf->last_row_data = row_packet->fields;
876 			result->unbuf->last_row_buffer = row_packet->row_buffer;
877 			row_packet->fields = NULL;
878 			row_packet->row_buffer = NULL;
879 
880 			if (PASS != result->unbuf->m.row_decoder(result->unbuf->last_row_buffer,
881 									result->unbuf->last_row_data,
882 									row_packet->field_count,
883 									row_packet->fields_metadata,
884 									result->conn->options->int_and_float_native,
885 									result->conn->stats TSRMLS_CC))
886 			{
887 				DBG_RETURN(FAIL);
888 			}
889 
890 			for (i = 0; i < field_count; i++) {
891 				if (stmt->result_bind[i].bound == TRUE) {
892 					zval *data = result->unbuf->last_row_data[i];
893 					/*
894 					  stmt->result_bind[i].zv has been already destructed
895 					  in result->unbuf->m.free_last_data()
896 					*/
897 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
898 					zval_dtor(stmt->result_bind[i].zv);
899 #endif
900 					if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) {
901 						if ((Z_TYPE_P(data) == IS_STRING) && (meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) {
902 							meta->fields[i].max_length = Z_STRLEN_P(data);
903 						}
904 						stmt->result_bind[i].zv->value = data->value;
905 						/* copied data, thus also the ownership. Thus null data */
906 						ZVAL_NULL(data);
907 					}
908 				}
909 			}
910 			MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_UNBUF);
911 		} else {
912 			DBG_INF("skipping extraction");
913 			/*
914 			  Data has been allocated and usually result->unbuf->m.free_last_data()
915 			  frees it but we can't call this function as it will cause problems with
916 			  the bound variables. Thus we need to do part of what it does or Zend will
917 			  report leaks.
918 			*/
919 			row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC);
920 			row_packet->row_buffer = NULL;
921 		}
922 
923 		result->unbuf->row_count++;
924 		*fetched_anything = TRUE;
925 	} else if (ret == FAIL) {
926 		if (row_packet->error_info.error_no) {
927 			COPY_CLIENT_ERROR(*stmt->conn->error_info, row_packet->error_info);
928 			COPY_CLIENT_ERROR(*stmt->error_info, row_packet->error_info);
929 		}
930 		CONN_SET_STATE(result->conn, CONN_READY);
931 		result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
932 	} else if (row_packet->eof) {
933 		DBG_INF("EOF");
934 		/* Mark the connection as usable again */
935 		result->unbuf->eof_reached = TRUE;
936 		memset(result->conn->upsert_status, 0, sizeof(*result->conn->upsert_status));
937 		result->conn->upsert_status->warning_count = row_packet->warning_count;
938 		result->conn->upsert_status->server_status = row_packet->server_status;
939 		/*
940 		  result->row_packet will be cleaned when
941 		  destroying the result object
942 		*/
943 		if (result->conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
944 			CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
945 		} else {
946 			CONN_SET_STATE(result->conn, CONN_READY);
947 		}
948 	}
949 
950 	DBG_INF_FMT("ret=%s fetched_anything=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
951 	DBG_RETURN(ret);
952 }
953 /* }}} */
954 
955 
956 /* {{{ mysqlnd_stmt::use_result */
957 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,use_result)958 MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s TSRMLS_DC)
959 {
960 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
961 	MYSQLND_RES * result;
962 	MYSQLND_CONN_DATA * conn;
963 
964 	DBG_ENTER("mysqlnd_stmt::use_result");
965 	if (!stmt || !stmt->conn || !stmt->result) {
966 		DBG_RETURN(NULL);
967 	}
968 	DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
969 
970 	conn = stmt->conn;
971 
972 	if (!stmt->field_count ||
973 		(!stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_FETCHING_DATA) ||
974 		(stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_READY) ||
975 		(stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE))
976 	{
977 		SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
978 						 UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
979 		DBG_ERR("command out of sync");
980 		DBG_RETURN(NULL);
981 	}
982 
983 	SET_EMPTY_ERROR(*stmt->error_info);
984 
985 	MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_PS_UNBUFFERED_SETS);
986 	result = stmt->result;
987 
988 	result->m.use_result(stmt->result, TRUE TSRMLS_CC);
989 	result->unbuf->m.fetch_row	= stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor:
990 											   mysqlnd_stmt_fetch_row_unbuffered;
991 	stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
992 
993 	DBG_INF_FMT("%p", result);
994 	DBG_RETURN(result);
995 }
996 /* }}} */
997 
998 
999 #define STMT_ID_LENGTH 4
1000 
1001 /* {{{ mysqlnd_fetch_row_cursor */
1002 enum_func_status
mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)1003 mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC)
1004 {
1005 	enum_func_status ret;
1006 	MYSQLND_STMT * s = (MYSQLND_STMT *) param;
1007 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1008 	zend_uchar buf[STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */];
1009 	MYSQLND_PACKET_ROW * row_packet;
1010 
1011 	DBG_ENTER("mysqlnd_fetch_stmt_row_cursor");
1012 
1013 	if (!stmt || !stmt->conn || !result || !result->conn || !result->unbuf) {
1014 		DBG_ERR("no statement");
1015 		DBG_RETURN(FAIL);
1016 	}
1017 
1018 	DBG_INF_FMT("stmt=%lu flags=%u", stmt->stmt_id, flags);
1019 
1020 	if (stmt->state < MYSQLND_STMT_USER_FETCHING) {
1021 		/* Only initted - error */
1022 		SET_CLIENT_ERROR(*stmt->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
1023 						mysqlnd_out_of_sync);
1024 		DBG_ERR("command out of sync");
1025 		DBG_RETURN(FAIL);
1026 	}
1027 	if (!(row_packet = result->unbuf->row_packet)) {
1028 		DBG_RETURN(FAIL);
1029 	}
1030 
1031 	SET_EMPTY_ERROR(*stmt->error_info);
1032 	SET_EMPTY_ERROR(*stmt->conn->error_info);
1033 
1034 	int4store(buf, stmt->stmt_id);
1035 	int4store(buf + STMT_ID_LENGTH, 1); /* for now fetch only one row */
1036 
1037 	if (FAIL == stmt->conn->m->simple_command(stmt->conn, COM_STMT_FETCH, buf, sizeof(buf),
1038 											  PROT_LAST /* we will handle the response packet*/,
1039 											  FALSE, TRUE TSRMLS_CC)) {
1040 		COPY_CLIENT_ERROR(*stmt->error_info, *stmt->conn->error_info);
1041 		DBG_RETURN(FAIL);
1042 	}
1043 
1044 	row_packet->skip_extraction = stmt->result_bind? FALSE:TRUE;
1045 
1046 	memset(stmt->upsert_status, 0, sizeof(*stmt->upsert_status));
1047 	if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
1048 		const MYSQLND_RES_METADATA * const meta = result->meta;
1049 		unsigned int i, field_count = result->field_count;
1050 
1051 		if (!row_packet->skip_extraction) {
1052 			result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC);
1053 
1054 			result->unbuf->last_row_data = row_packet->fields;
1055 			result->unbuf->last_row_buffer = row_packet->row_buffer;
1056 			row_packet->fields = NULL;
1057 			row_packet->row_buffer = NULL;
1058 
1059 			if (PASS != result->unbuf->m.row_decoder(result->unbuf->last_row_buffer,
1060 									  result->unbuf->last_row_data,
1061 									  row_packet->field_count,
1062 									  row_packet->fields_metadata,
1063 									  result->conn->options->int_and_float_native,
1064 									  result->conn->stats TSRMLS_CC))
1065 			{
1066 				DBG_RETURN(FAIL);
1067 			}
1068 
1069 			/* If no result bind, do nothing. We consumed the data */
1070 			for (i = 0; i < field_count; i++) {
1071 				if (stmt->result_bind[i].bound == TRUE) {
1072 					zval *data = result->unbuf->last_row_data[i];
1073 					/*
1074 					  stmt->result_bind[i].zv has been already destructed
1075 					  in result->unbuf->m.free_last_data()
1076 					*/
1077 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
1078 					zval_dtor(stmt->result_bind[i].zv);
1079 #endif
1080 					DBG_INF_FMT("i=%u bound_var=%p type=%u refc=%u", i, stmt->result_bind[i].zv,
1081 								Z_TYPE_P(data), Z_REFCOUNT_P(stmt->result_bind[i].zv));
1082 					if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data))) {
1083 						if ((Z_TYPE_P(data) == IS_STRING) && (meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) {
1084 							meta->fields[i].max_length = Z_STRLEN_P(data);
1085 						}
1086 						stmt->result_bind[i].zv->value = data->value;
1087 						/* copied data, thus also the ownership. Thus null data */
1088 						ZVAL_NULL(data);
1089 					}
1090 				}
1091 			}
1092 		} else {
1093 			DBG_INF("skipping extraction");
1094 			/*
1095 			  Data has been allocated and usually result->unbuf->m.free_last_data()
1096 			  frees it but we can't call this function as it will cause problems with
1097 			  the bound variables. Thus we need to do part of what it does or Zend will
1098 			  report leaks.
1099 			*/
1100 			row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC);
1101 			row_packet->row_buffer = NULL;
1102 		}
1103 		/* We asked for one row, the next one should be EOF, eat it */
1104 		ret = PACKET_READ(row_packet, result->conn);
1105 		if (row_packet->row_buffer) {
1106 			row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC);
1107 			row_packet->row_buffer = NULL;
1108 		}
1109 		MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR);
1110 
1111 		result->unbuf->row_count++;
1112 		*fetched_anything = TRUE;
1113 	} else {
1114 		*fetched_anything = FALSE;
1115 
1116 		stmt->upsert_status->warning_count =
1117 			stmt->conn->upsert_status->warning_count =
1118 				row_packet->warning_count;
1119 
1120 		stmt->upsert_status->server_status =
1121 			stmt->conn->upsert_status->server_status =
1122 				row_packet->server_status;
1123 
1124 		result->unbuf->eof_reached = row_packet->eof;
1125 	}
1126 	stmt->upsert_status->warning_count =
1127 		stmt->conn->upsert_status->warning_count =
1128 			row_packet->warning_count;
1129 	stmt->upsert_status->server_status =
1130 		stmt->conn->upsert_status->server_status =
1131 			row_packet->server_status;
1132 
1133 	DBG_INF_FMT("ret=%s fetched=%u server_status=%u warnings=%u eof=%u",
1134 				ret == PASS? "PASS":"FAIL", *fetched_anything,
1135 				row_packet->server_status, row_packet->warning_count,
1136 				result->unbuf->eof_reached);
1137 	DBG_RETURN(ret);
1138 }
1139 /* }}} */
1140 
1141 
1142 /* {{{ mysqlnd_stmt::fetch */
1143 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,fetch)1144 MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const s, zend_bool * const fetched_anything TSRMLS_DC)
1145 {
1146 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1147 	enum_func_status ret;
1148 	DBG_ENTER("mysqlnd_stmt::fetch");
1149 	if (!stmt || !stmt->conn) {
1150 		DBG_RETURN(FAIL);
1151 	}
1152 	DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1153 
1154 	if (!stmt->result ||
1155 		stmt->state < MYSQLND_STMT_WAITING_USE_OR_STORE) {
1156 		SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1157 
1158 		DBG_ERR("command out of sync");
1159 		DBG_RETURN(FAIL);
1160 	} else if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1161 		/* Execute only once. We have to free the previous contents of user's bound vars */
1162 
1163 		stmt->default_rset_handler(s TSRMLS_CC);
1164 	}
1165 	stmt->state = MYSQLND_STMT_USER_FETCHING;
1166 
1167 	SET_EMPTY_ERROR(*stmt->error_info);
1168 	SET_EMPTY_ERROR(*stmt->conn->error_info);
1169 
1170 	DBG_INF_FMT("result_bind=%p separated_once=%u", stmt->result_bind, stmt->result_zvals_separated_once);
1171 	/*
1172 	  The user might have not bound any variables for result.
1173 	  Do the binding once she does it.
1174 	*/
1175 	if (stmt->result_bind && !stmt->result_zvals_separated_once) {
1176 		unsigned int i;
1177 		/*
1178 		  mysqlnd_stmt_store_result() has been called free the bind
1179 		  variables to prevent leaking of their previous content.
1180 		*/
1181 		for (i = 0; i < stmt->result->field_count; i++) {
1182 			if (stmt->result_bind[i].bound == TRUE) {
1183 				zval_dtor(stmt->result_bind[i].zv);
1184 				ZVAL_NULL(stmt->result_bind[i].zv);
1185 			}
1186 		}
1187 		stmt->result_zvals_separated_once = TRUE;
1188 	}
1189 
1190 	ret = stmt->result->m.fetch_row(stmt->result, (void*)s, 0, fetched_anything TSRMLS_CC);
1191 	DBG_RETURN(ret);
1192 }
1193 /* }}} */
1194 
1195 
1196 /* {{{ mysqlnd_stmt::reset */
1197 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,reset)1198 MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const s TSRMLS_DC)
1199 {
1200 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1201 	enum_func_status ret = PASS;
1202 	zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */];
1203 
1204 	DBG_ENTER("mysqlnd_stmt::reset");
1205 	if (!stmt || !stmt->conn) {
1206 		DBG_RETURN(FAIL);
1207 	}
1208 	DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1209 
1210 	SET_EMPTY_ERROR(*stmt->error_info);
1211 	SET_EMPTY_ERROR(*stmt->conn->error_info);
1212 
1213 	if (stmt->stmt_id) {
1214 		MYSQLND_CONN_DATA * conn = stmt->conn;
1215 		if (stmt->param_bind) {
1216 			unsigned int i;
1217 			DBG_INF("resetting long data");
1218 			/* Reset Long Data */
1219 			for (i = 0; i < stmt->param_count; i++) {
1220 				if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
1221 					stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1222 				}
1223 			}
1224 		}
1225 
1226 		s->m->flush(s TSRMLS_CC);
1227 
1228 		/*
1229 		  Don't free now, let the result be usable. When the stmt will again be
1230 		  executed then the result set will be cleaned, the bound variables will
1231 		  be separated before that.
1232 		*/
1233 
1234 		int4store(cmd_buf, stmt->stmt_id);
1235 		if (CONN_GET_STATE(conn) == CONN_READY &&
1236 			FAIL == (ret = conn->m->simple_command(conn, COM_STMT_RESET, cmd_buf,
1237 												  sizeof(cmd_buf), PROT_OK_PACKET,
1238 												  FALSE, TRUE TSRMLS_CC))) {
1239 			COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info);
1240 		}
1241 		*stmt->upsert_status = *conn->upsert_status;
1242 	}
1243 	DBG_INF(ret == PASS? "PASS":"FAIL");
1244 	DBG_RETURN(ret);
1245 }
1246 /* }}} */
1247 
1248 
1249 /* {{{ mysqlnd_stmt::flush */
1250 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,flush)1251 MYSQLND_METHOD(mysqlnd_stmt, flush)(MYSQLND_STMT * const s TSRMLS_DC)
1252 {
1253 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1254 	enum_func_status ret = PASS;
1255 
1256 	DBG_ENTER("mysqlnd_stmt::flush");
1257 	if (!stmt || !stmt->conn) {
1258 		DBG_RETURN(FAIL);
1259 	}
1260 	DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1261 
1262 	if (stmt->stmt_id) {
1263 		/*
1264 		  If the user decided to close the statement right after execute()
1265 		  We have to call the appropriate use_result() or store_result() and
1266 		  clean.
1267 		*/
1268 		do {
1269 			if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1270 				DBG_INF("fetching result set header");
1271 				stmt->default_rset_handler(s TSRMLS_CC);
1272 				stmt->state = MYSQLND_STMT_USER_FETCHING;
1273 			}
1274 
1275 			if (stmt->result) {
1276 				DBG_INF("skipping result");
1277 				stmt->result->m.skip_result(stmt->result TSRMLS_CC);
1278 			}
1279 		} while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
1280 	}
1281 	DBG_INF(ret == PASS? "PASS":"FAIL");
1282 	DBG_RETURN(ret);
1283 }
1284 /* }}} */
1285 
1286 
1287 /* {{{ mysqlnd_stmt::send_long_data */
1288 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,send_long_data)1289 MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const s, unsigned int param_no,
1290 							 				 const char * const data, unsigned long length TSRMLS_DC)
1291 {
1292 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1293 	enum_func_status ret = FAIL;
1294 	MYSQLND_CONN_DATA * conn;
1295 	zend_uchar * cmd_buf;
1296 	enum php_mysqlnd_server_command cmd = COM_STMT_SEND_LONG_DATA;
1297 
1298 	DBG_ENTER("mysqlnd_stmt::send_long_data");
1299 	if (!stmt || !stmt->conn) {
1300 		DBG_RETURN(FAIL);
1301 	}
1302 	DBG_INF_FMT("stmt=%lu param_no=%u data_len=%lu", stmt->stmt_id, param_no, length);
1303 
1304 	conn = stmt->conn;
1305 
1306 	SET_EMPTY_ERROR(*stmt->error_info);
1307 	SET_EMPTY_ERROR(*stmt->conn->error_info);
1308 
1309 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1310 		SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1311 		DBG_ERR("not prepared");
1312 		DBG_RETURN(FAIL);
1313 	}
1314 	if (!stmt->param_bind) {
1315 		SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
1316 		DBG_ERR("command out of sync");
1317 		DBG_RETURN(FAIL);
1318 	}
1319 	if (param_no >= stmt->param_count) {
1320 		SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1321 		DBG_ERR("invalid param_no");
1322 		DBG_RETURN(FAIL);
1323 	}
1324 	if (stmt->param_bind[param_no].type != MYSQL_TYPE_LONG_BLOB) {
1325 		SET_STMT_ERROR(stmt, CR_INVALID_BUFFER_USE, UNKNOWN_SQLSTATE, mysqlnd_not_bound_as_blob);
1326 		DBG_ERR("param_no is not of a blob type");
1327 		DBG_RETURN(FAIL);
1328 	}
1329 
1330 	/*
1331 	  XXX:	Unfortunately we have to allocate additional buffer to be able the
1332 			additional data, which is like a header inside the payload.
1333 			This should be optimised, but it will be a pervasive change, so
1334 			conn->m->simple_command() will accept not a buffer, but actually MYSQLND_STRING*
1335 			terminated by NULL, to send. If the strings are not big, we can collapse them
1336 			on the buffer every connection has, but otherwise we will just send them
1337 			one by one to the wire.
1338 	*/
1339 
1340 	if (CONN_GET_STATE(conn) == CONN_READY) {
1341 		size_t packet_len;
1342 		cmd_buf = mnd_emalloc(packet_len = STMT_ID_LENGTH + 2 + length);
1343 		if (cmd_buf) {
1344 			stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED;
1345 
1346 			int4store(cmd_buf, stmt->stmt_id);
1347 			int2store(cmd_buf + STMT_ID_LENGTH, param_no);
1348 			memcpy(cmd_buf + STMT_ID_LENGTH + 2, data, length);
1349 
1350 			/* COM_STMT_SEND_LONG_DATA doesn't send an OK packet*/
1351 			ret = conn->m->simple_command(conn, cmd, cmd_buf, packet_len, PROT_LAST , FALSE, TRUE TSRMLS_CC);
1352 			mnd_efree(cmd_buf);
1353 			if (FAIL == ret) {
1354 				COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info);
1355 			}
1356 		} else {
1357 			ret = FAIL;
1358 			SET_OOM_ERROR(*stmt->error_info);
1359 			SET_OOM_ERROR(*conn->error_info);
1360 		}
1361 		/*
1362 		  Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not
1363 		  sent response packets. According to documentation the only way to get an error
1364 		  is to have out-of-memory on the server-side. However, that's not true, as if
1365 		  max_allowed_packet_size is smaller than the chunk being sent to the server, the
1366 		  latter will complain with an error message. However, normally we don't expect
1367 		  an error message, thus we continue. When sending the next command, which expects
1368 		  response we will read the unexpected data and error message will look weird.
1369 		  Therefore we do non-blocking read to clean the line, if there is a need.
1370 		  Nevertheless, there is a built-in protection when sending a command packet, that
1371 		  checks if the line is clear - useful for debug purposes and to be switched off
1372 		  in release builds.
1373 
1374 		  Maybe we can make it automatic by checking what's the value of
1375 		  max_allowed_packet_size on the server and resending the data.
1376 		*/
1377 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
1378 #if HAVE_USLEEP && !defined(PHP_WIN32)
1379 		usleep(120000);
1380 #endif
1381 		if ((packet_len = conn->net->m.consume_uneaten_data(conn->net, cmd TSRMLS_CC))) {
1382 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "There was an error "
1383 							 "while sending long data. Probably max_allowed_packet_size "
1384 							 "is smaller than the data. You have to increase it or send "
1385 							 "smaller chunks of data. Answer was "MYSQLND_SZ_T_SPEC" bytes long.", packet_len);
1386 			SET_STMT_ERROR(stmt, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE,
1387 							"Server responded to COM_STMT_SEND_LONG_DATA.");
1388 			ret = FAIL;
1389 		}
1390 #endif
1391 	}
1392 
1393 	DBG_INF(ret == PASS? "PASS":"FAIL");
1394 	DBG_RETURN(ret);
1395 }
1396 /* }}} */
1397 
1398 
1399 /* {{{ mysqlnd_stmt::bind_parameters */
1400 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_parameters)1401 MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * const param_bind TSRMLS_DC)
1402 {
1403 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1404 	DBG_ENTER("mysqlnd_stmt::bind_param");
1405 	if (!stmt || !stmt->conn) {
1406 		DBG_RETURN(FAIL);
1407 	}
1408 	DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
1409 
1410 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1411 		SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1412 		DBG_ERR("not prepared");
1413 		if (param_bind) {
1414 			s->m->free_parameter_bind(s, param_bind TSRMLS_CC);
1415 		}
1416 		DBG_RETURN(FAIL);
1417 	}
1418 
1419 	SET_EMPTY_ERROR(*stmt->error_info);
1420 	SET_EMPTY_ERROR(*stmt->conn->error_info);
1421 
1422 	if (stmt->param_count) {
1423 		unsigned int i = 0;
1424 
1425 		if (!param_bind) {
1426 			SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, "Re-binding (still) not supported");
1427 			DBG_ERR("Re-binding (still) not supported");
1428 			DBG_RETURN(FAIL);
1429 		} else if (stmt->param_bind) {
1430 			DBG_INF("Binding");
1431 			/*
1432 			  There is already result bound.
1433 			  Forbid for now re-binding!!
1434 			*/
1435 			for (i = 0; i < stmt->param_count; i++) {
1436 				/*
1437 				  We may have the last reference, then call zval_ptr_dtor() or we may leak memory.
1438 				  Switching from bind_one_parameter to bind_parameters may result in zv being NULL
1439 				*/
1440 				if (stmt->param_bind[i].zv) {
1441 					zval_ptr_dtor(&stmt->param_bind[i].zv);
1442 				}
1443 			}
1444 			if (stmt->param_bind != param_bind) {
1445 				s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC);
1446 			}
1447 		}
1448 
1449 		stmt->param_bind = param_bind;
1450 		for (i = 0; i < stmt->param_count; i++) {
1451 			/* The client will use stmt_send_long_data */
1452 			DBG_INF_FMT("%u is of type %u", i, stmt->param_bind[i].type);
1453 			/* Prevent from freeing */
1454 			/* Don't update is_ref, or we will leak during conversion */
1455 			Z_ADDREF_P(stmt->param_bind[i].zv);
1456 			stmt->param_bind[i].flags = 0;
1457 			if (stmt->param_bind[i].type == MYSQL_TYPE_LONG_BLOB) {
1458 				stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1459 			}
1460 		}
1461 		stmt->send_types_to_server = 1;
1462 	}
1463 	DBG_INF("PASS");
1464 	DBG_RETURN(PASS);
1465 }
1466 /* }}} */
1467 
1468 
1469 /* {{{ mysqlnd_stmt::bind_one_parameter */
1470 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_parameter)1471 MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter)(MYSQLND_STMT * const s, unsigned int param_no,
1472 												 zval * const zv, zend_uchar type TSRMLS_DC)
1473 {
1474 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1475 	DBG_ENTER("mysqlnd_stmt::bind_one_parameter");
1476 	if (!stmt || !stmt->conn) {
1477 		DBG_RETURN(FAIL);
1478 	}
1479 	DBG_INF_FMT("stmt=%lu param_no=%u param_count=%u type=%u", stmt->stmt_id, param_no, stmt->param_count, type);
1480 
1481 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1482 		SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1483 		DBG_ERR("not prepared");
1484 		DBG_RETURN(FAIL);
1485 	}
1486 
1487 	if (param_no >= stmt->param_count) {
1488 		SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1489 		DBG_ERR("invalid param_no");
1490 		DBG_RETURN(FAIL);
1491 	}
1492 	SET_EMPTY_ERROR(*stmt->error_info);
1493 	SET_EMPTY_ERROR(*stmt->conn->error_info);
1494 
1495 	if (stmt->param_count) {
1496 		if (!stmt->param_bind) {
1497 			stmt->param_bind = mnd_pecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND), stmt->persistent);
1498 			if (!stmt->param_bind) {
1499 				DBG_RETURN(FAIL);
1500 			}
1501 		}
1502 
1503 		/* Prevent from freeing */
1504 		/* Don't update is_ref, or we will leak during conversion */
1505 		Z_ADDREF_P(zv);
1506 		DBG_INF("Binding");
1507 		/* Release what we had, if we had */
1508 		if (stmt->param_bind[param_no].zv) {
1509 			zval_ptr_dtor(&stmt->param_bind[param_no].zv);
1510 		}
1511 		if (type == MYSQL_TYPE_LONG_BLOB) {
1512 			/* The client will use stmt_send_long_data */
1513 			stmt->param_bind[param_no].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
1514 		}
1515 		stmt->param_bind[param_no].zv = zv;
1516 		stmt->param_bind[param_no].type = type;
1517 
1518 		stmt->send_types_to_server = 1;
1519 	}
1520 	DBG_INF("PASS");
1521 	DBG_RETURN(PASS);
1522 }
1523 /* }}} */
1524 
1525 
1526 /* {{{ mysqlnd_stmt::refresh_bind_param */
1527 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,refresh_bind_param)1528 MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param)(MYSQLND_STMT * const s TSRMLS_DC)
1529 {
1530 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1531 	DBG_ENTER("mysqlnd_stmt::refresh_bind_param");
1532 	if (!stmt || !stmt->conn) {
1533 		DBG_RETURN(FAIL);
1534 	}
1535 	DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
1536 
1537 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1538 		SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1539 		DBG_ERR("not prepared");
1540 		DBG_RETURN(FAIL);
1541 	}
1542 
1543 	SET_EMPTY_ERROR(*stmt->error_info);
1544 	SET_EMPTY_ERROR(*stmt->conn->error_info);
1545 
1546 	if (stmt->param_count) {
1547 		stmt->send_types_to_server = 1;
1548 	}
1549 	DBG_RETURN(PASS);
1550 }
1551 /* }}} */
1552 
1553 
1554 /* {{{ mysqlnd_stmt::bind_result */
1555 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_result)1556 MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const s,
1557 										  MYSQLND_RESULT_BIND * const result_bind TSRMLS_DC)
1558 {
1559 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1560 	DBG_ENTER("mysqlnd_stmt::bind_result");
1561 	if (!stmt || !stmt->conn) {
1562 		DBG_RETURN(FAIL);
1563 	}
1564 	DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
1565 
1566 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1567 		SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1568 		if (result_bind) {
1569 			s->m->free_result_bind(s, result_bind TSRMLS_CC);
1570 		}
1571 		DBG_ERR("not prepared");
1572 		DBG_RETURN(FAIL);
1573 	}
1574 
1575 	SET_EMPTY_ERROR(*stmt->error_info);
1576 	SET_EMPTY_ERROR(*stmt->conn->error_info);
1577 
1578 	if (stmt->field_count) {
1579 		unsigned int i = 0;
1580 
1581 		if (!result_bind) {
1582 			DBG_ERR("no result bind passed");
1583 			DBG_RETURN(FAIL);
1584 		}
1585 
1586 		mysqlnd_stmt_separate_result_bind(s TSRMLS_CC);
1587 		stmt->result_zvals_separated_once = FALSE;
1588 		stmt->result_bind = result_bind;
1589 		for (i = 0; i < stmt->field_count; i++) {
1590 			/* Prevent from freeing */
1591 			Z_ADDREF_P(stmt->result_bind[i].zv);
1592 			DBG_INF_FMT("ref of %p = %u", stmt->result_bind[i].zv, Z_REFCOUNT_P(stmt->result_bind[i].zv));
1593 			/*
1594 			  Don't update is_ref !!! it's not our job
1595 			  Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1596 			  will fail.
1597 			*/
1598 			stmt->result_bind[i].bound = TRUE;
1599 		}
1600 	} else if (result_bind) {
1601 		s->m->free_result_bind(s, result_bind TSRMLS_CC);
1602 	}
1603 	DBG_INF("PASS");
1604 	DBG_RETURN(PASS);
1605 }
1606 /* }}} */
1607 
1608 
1609 /* {{{ mysqlnd_stmt::bind_result */
1610 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,bind_one_result)1611 MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned int param_no TSRMLS_DC)
1612 {
1613 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1614 	DBG_ENTER("mysqlnd_stmt::bind_result");
1615 	if (!stmt || !stmt->conn) {
1616 		DBG_RETURN(FAIL);
1617 	}
1618 	DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
1619 
1620 	if (stmt->state < MYSQLND_STMT_PREPARED) {
1621 		SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
1622 		DBG_ERR("not prepared");
1623 		DBG_RETURN(FAIL);
1624 	}
1625 
1626 	if (param_no >= stmt->field_count) {
1627 		SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
1628 		DBG_ERR("invalid param_no");
1629 		DBG_RETURN(FAIL);
1630 	}
1631 
1632 	SET_EMPTY_ERROR(*stmt->error_info);
1633 	SET_EMPTY_ERROR(*stmt->conn->error_info);
1634 
1635 	if (stmt->field_count) {
1636 		mysqlnd_stmt_separate_one_result_bind(s, param_no TSRMLS_CC);
1637 		/* Guaranteed is that stmt->result_bind is NULL */
1638 		if (!stmt->result_bind) {
1639 			stmt->result_bind = mnd_pecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND), stmt->persistent);
1640 		} else {
1641 			stmt->result_bind = mnd_perealloc(stmt->result_bind, stmt->field_count * sizeof(MYSQLND_RESULT_BIND), stmt->persistent);
1642 		}
1643 		if (!stmt->result_bind) {
1644 			DBG_RETURN(FAIL);
1645 		}
1646 		ALLOC_INIT_ZVAL(stmt->result_bind[param_no].zv);
1647 		/*
1648 		  Don't update is_ref !!! it's not our job
1649 		  Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
1650 		  will fail.
1651 		*/
1652 		stmt->result_bind[param_no].bound = TRUE;
1653 	}
1654 	DBG_INF("PASS");
1655 	DBG_RETURN(PASS);
1656 }
1657 /* }}} */
1658 
1659 
1660 /* {{{ mysqlnd_stmt::insert_id */
1661 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,insert_id)1662 MYSQLND_METHOD(mysqlnd_stmt, insert_id)(const MYSQLND_STMT * const s TSRMLS_DC)
1663 {
1664 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1665 	return stmt? stmt->upsert_status->last_insert_id : 0;
1666 }
1667 /* }}} */
1668 
1669 
1670 /* {{{ mysqlnd_stmt::affected_rows */
1671 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,affected_rows)1672 MYSQLND_METHOD(mysqlnd_stmt, affected_rows)(const MYSQLND_STMT * const s TSRMLS_DC)
1673 {
1674 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1675 	return stmt? stmt->upsert_status->affected_rows : 0;
1676 }
1677 /* }}} */
1678 
1679 
1680 /* {{{ mysqlnd_stmt::num_rows */
1681 static uint64_t
MYSQLND_METHOD(mysqlnd_stmt,num_rows)1682 MYSQLND_METHOD(mysqlnd_stmt, num_rows)(const MYSQLND_STMT * const s TSRMLS_DC)
1683 {
1684 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1685 	return stmt && stmt->result? mysqlnd_num_rows(stmt->result):0;
1686 }
1687 /* }}} */
1688 
1689 
1690 /* {{{ mysqlnd_stmt::warning_count */
1691 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,warning_count)1692 MYSQLND_METHOD(mysqlnd_stmt, warning_count)(const MYSQLND_STMT * const s TSRMLS_DC)
1693 {
1694 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1695 	return stmt? stmt->upsert_status->warning_count : 0;
1696 }
1697 /* }}} */
1698 
1699 
1700 /* {{{ mysqlnd_stmt::server_status */
1701 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,server_status)1702 MYSQLND_METHOD(mysqlnd_stmt, server_status)(const MYSQLND_STMT * const s TSRMLS_DC)
1703 {
1704 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1705 	return stmt? stmt->upsert_status->server_status : 0;
1706 }
1707 /* }}} */
1708 
1709 
1710 /* {{{ mysqlnd_stmt::field_count */
1711 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,field_count)1712 MYSQLND_METHOD(mysqlnd_stmt, field_count)(const MYSQLND_STMT * const s TSRMLS_DC)
1713 {
1714 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1715 	return stmt? stmt->field_count : 0;
1716 }
1717 /* }}} */
1718 
1719 
1720 /* {{{ mysqlnd_stmt::param_count */
1721 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,param_count)1722 MYSQLND_METHOD(mysqlnd_stmt, param_count)(const MYSQLND_STMT * const s TSRMLS_DC)
1723 {
1724 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1725 	return stmt? stmt->param_count : 0;
1726 }
1727 /* }}} */
1728 
1729 
1730 /* {{{ mysqlnd_stmt::errno */
1731 static unsigned int
MYSQLND_METHOD(mysqlnd_stmt,errno)1732 MYSQLND_METHOD(mysqlnd_stmt, errno)(const MYSQLND_STMT * const s TSRMLS_DC)
1733 {
1734 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1735 	return stmt? stmt->error_info->error_no : 0;
1736 }
1737 /* }}} */
1738 
1739 
1740 /* {{{ mysqlnd_stmt::error */
1741 static const char *
MYSQLND_METHOD(mysqlnd_stmt,error)1742 MYSQLND_METHOD(mysqlnd_stmt, error)(const MYSQLND_STMT * const s TSRMLS_DC)
1743 {
1744 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1745 	return stmt? stmt->error_info->error : 0;
1746 }
1747 /* }}} */
1748 
1749 
1750 /* {{{ mysqlnd_stmt::sqlstate */
1751 static const char *
MYSQLND_METHOD(mysqlnd_stmt,sqlstate)1752 MYSQLND_METHOD(mysqlnd_stmt, sqlstate)(const MYSQLND_STMT * const s TSRMLS_DC)
1753 {
1754 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1755 	return stmt && stmt->error_info->sqlstate[0] ? stmt->error_info->sqlstate:MYSQLND_SQLSTATE_NULL;
1756 }
1757 /* }}} */
1758 
1759 
1760 /* {{{ mysqlnd_stmt::data_seek */
1761 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,data_seek)1762 MYSQLND_METHOD(mysqlnd_stmt, data_seek)(const MYSQLND_STMT * const s, uint64_t row TSRMLS_DC)
1763 {
1764 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1765 	return stmt && stmt->result? stmt->result->m.seek_data(stmt->result, row TSRMLS_CC) : FAIL;
1766 }
1767 /* }}} */
1768 
1769 
1770 /* {{{ mysqlnd_stmt::param_metadata */
1771 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,param_metadata)1772 MYSQLND_METHOD(mysqlnd_stmt, param_metadata)(MYSQLND_STMT * const s TSRMLS_DC)
1773 {
1774 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1775 	if (!stmt || !stmt->param_count) {
1776 		return NULL;
1777 	}
1778 	return NULL;
1779 }
1780 /* }}} */
1781 
1782 
1783 /* {{{ mysqlnd_stmt::result_metadata */
1784 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_stmt,result_metadata)1785 MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s TSRMLS_DC)
1786 {
1787 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1788 	MYSQLND_RES *result;
1789 
1790 	DBG_ENTER("mysqlnd_stmt::result_metadata");
1791 	if (!stmt) {
1792 		DBG_RETURN(NULL);
1793 	}
1794 	DBG_INF_FMT("stmt=%u field_count=%u", stmt->stmt_id, stmt->field_count);
1795 
1796 	if (!stmt->field_count || !stmt->conn || !stmt->result || !stmt->result->meta) {
1797 		DBG_INF("NULL");
1798 		DBG_RETURN(NULL);
1799 	}
1800 
1801 	if (stmt->update_max_length && stmt->result->stored_data) {
1802 		/* stored result, we have to update the max_length before we clone the meta data :( */
1803 		stmt->result->stored_data->m.initialize_result_set_rest(stmt->result->stored_data, stmt->result->meta, stmt->conn->stats,
1804 																stmt->conn->options->int_and_float_native TSRMLS_CC);
1805 	}
1806 	/*
1807 	  TODO: This implementation is kind of a hack,
1808 			find a better way to do it. In different functions I have put
1809 			fuses to check for result->m.fetch_row() being NULL. This should
1810 			be handled in a better way.
1811 
1812 	  In the meantime we don't need a zval cache reference for this fake
1813 	  result set, so we don't get one.
1814 	*/
1815 	do {
1816 		result = stmt->conn->m->result_init(stmt->field_count, stmt->persistent TSRMLS_CC);
1817 		if (!result) {
1818 			break;
1819 		}
1820 		result->type = MYSQLND_RES_NORMAL;
1821 		result->unbuf = mysqlnd_result_unbuffered_init(stmt->field_count, TRUE, result->persistent TSRMLS_CC);
1822 		if (!result->unbuf) {
1823 			break;
1824 		}
1825 		result->unbuf->eof_reached = TRUE;
1826 		result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC);
1827 		if (!result->meta) {
1828 			break;
1829 		}
1830 
1831 		DBG_INF_FMT("result=%p", result);
1832 		DBG_RETURN(result);
1833 	} while (0);
1834 
1835 	SET_OOM_ERROR(*stmt->conn->error_info);
1836 	if (result) {
1837 		result->m.free_result(result, TRUE TSRMLS_CC);
1838 	}
1839 	DBG_RETURN(NULL);
1840 }
1841 /* }}} */
1842 
1843 
1844 /* {{{ mysqlnd_stmt::attr_set */
1845 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_set)1846 MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const s,
1847 									   enum mysqlnd_stmt_attr attr_type,
1848 									   const void * const value TSRMLS_DC)
1849 {
1850 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1851 	DBG_ENTER("mysqlnd_stmt::attr_set");
1852 	if (!stmt) {
1853 		DBG_RETURN(FAIL);
1854 	}
1855 	DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type);
1856 
1857 	switch (attr_type) {
1858 		case STMT_ATTR_UPDATE_MAX_LENGTH:{
1859 			zend_uchar bval = *(zend_uchar *) value;
1860 			/*
1861 			  XXX : libmysql uses my_bool, but mysqli uses ulong as storage on the stack
1862 			  and mysqlnd won't be used out of the scope of PHP -> use ulong.
1863 			*/
1864 			stmt->update_max_length = bval? TRUE:FALSE;
1865 			break;
1866 		}
1867 		case STMT_ATTR_CURSOR_TYPE: {
1868 			unsigned int ival = *(unsigned int *) value;
1869 			if (ival > (unsigned long) CURSOR_TYPE_READ_ONLY) {
1870 				SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1871 				DBG_INF("FAIL");
1872 				DBG_RETURN(FAIL);
1873 			}
1874 			stmt->flags = ival;
1875 			break;
1876 		}
1877 		case STMT_ATTR_PREFETCH_ROWS: {
1878 			unsigned int ival = *(unsigned int *) value;
1879 			if (ival == 0) {
1880 				ival = MYSQLND_DEFAULT_PREFETCH_ROWS;
1881 			} else if (ival > 1) {
1882 				SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1883 				DBG_INF("FAIL");
1884 				DBG_RETURN(FAIL);
1885 			}
1886 			stmt->prefetch_rows = ival;
1887 			break;
1888 		}
1889 		default:
1890 			SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
1891 			DBG_RETURN(FAIL);
1892 	}
1893 	DBG_INF("PASS");
1894 	DBG_RETURN(PASS);
1895 }
1896 /* }}} */
1897 
1898 
1899 /* {{{ mysqlnd_stmt::attr_get */
1900 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,attr_get)1901 MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s,
1902 									   enum mysqlnd_stmt_attr attr_type,
1903 									   void * const value TSRMLS_DC)
1904 {
1905 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1906 	DBG_ENTER("mysqlnd_stmt::attr_set");
1907 	if (!stmt) {
1908 		DBG_RETURN(FAIL);
1909 	}
1910 	DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type);
1911 
1912 	switch (attr_type) {
1913 		case STMT_ATTR_UPDATE_MAX_LENGTH:
1914 			*(zend_bool *) value= stmt->update_max_length;
1915 			break;
1916 		case STMT_ATTR_CURSOR_TYPE:
1917 			*(unsigned long *) value= stmt->flags;
1918 			break;
1919 		case STMT_ATTR_PREFETCH_ROWS:
1920 			*(unsigned long *) value= stmt->prefetch_rows;
1921 			break;
1922 		default:
1923 			DBG_RETURN(FAIL);
1924 	}
1925 	DBG_INF_FMT("value=%lu", value);
1926 	DBG_RETURN(PASS);
1927 }
1928 /* }}} */
1929 
1930 
1931 /* free_result() doesn't actually free stmt->result but only the buffers */
1932 /* {{{ mysqlnd_stmt::free_result */
1933 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,free_result)1934 MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const s TSRMLS_DC)
1935 {
1936 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1937 	DBG_ENTER("mysqlnd_stmt::free_result");
1938 	if (!stmt || !stmt->conn) {
1939 		DBG_RETURN(FAIL);
1940 	}
1941 	DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
1942 
1943 	if (!stmt->result) {
1944 		DBG_INF("no result");
1945 		DBG_RETURN(PASS);
1946 	}
1947 
1948 	/*
1949 	  If right after execute() we have to call the appropriate
1950 	  use_result() or store_result() and clean.
1951 	*/
1952 	if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
1953 		DBG_INF("fetching result set header");
1954 		/* Do implicit use_result and then flush the result */
1955 		stmt->default_rset_handler = s->m->use_result;
1956 		stmt->default_rset_handler(s TSRMLS_CC);
1957 	}
1958 
1959 	if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
1960 		DBG_INF("skipping result");
1961 		/* Flush if anything is left and unbuffered set */
1962 		stmt->result->m.skip_result(stmt->result TSRMLS_CC);
1963 		/*
1964 		  Separate the bound variables, which point to the result set, then
1965 		  destroy the set.
1966 		*/
1967 		mysqlnd_stmt_separate_result_bind(s TSRMLS_CC);
1968 
1969 		/* Now we can destroy the result set */
1970 		stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC);
1971 	}
1972 
1973 	if (stmt->state > MYSQLND_STMT_PREPARED) {
1974 		/* As the buffers have been freed, we should go back to PREPARED */
1975 		stmt->state = MYSQLND_STMT_PREPARED;
1976 	}
1977 
1978 	/* Line is free! */
1979 	CONN_SET_STATE(stmt->conn, CONN_READY);
1980 
1981 	DBG_RETURN(PASS);
1982 }
1983 /* }}} */
1984 
1985 
1986 /* {{{ mysqlnd_stmt_separate_result_bind */
1987 static void
mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s TSRMLS_DC)1988 mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s TSRMLS_DC)
1989 {
1990 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
1991 	unsigned int i;
1992 
1993 	DBG_ENTER("mysqlnd_stmt_separate_result_bind");
1994 	if (!stmt) {
1995 		DBG_VOID_RETURN;
1996 	}
1997 	DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u", stmt->stmt_id, stmt->result_bind, stmt->field_count);
1998 
1999 	if (!stmt->result_bind) {
2000 		DBG_VOID_RETURN;
2001 	}
2002 
2003 	/*
2004 	  Because only the bound variables can point to our internal buffers, then
2005 	  separate or free only them. Free is possible because the user could have
2006 	  lost reference.
2007 	*/
2008 	for (i = 0; i < stmt->field_count; i++) {
2009 		/* Let's try with no cache */
2010 		if (stmt->result_bind[i].bound == TRUE) {
2011 			DBG_INF_FMT("%u has refcount=%u", i, Z_REFCOUNT_P(stmt->result_bind[i].zv));
2012 			/*
2013 			  We have to separate the actual zval value of the bound
2014 			  variable from our allocated zvals or we will face double-free
2015 			*/
2016 			if (Z_REFCOUNT_P(stmt->result_bind[i].zv) > 1) {
2017 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
2018 				zval_copy_ctor(stmt->result_bind[i].zv);
2019 #endif
2020 				zval_ptr_dtor(&stmt->result_bind[i].zv);
2021 			} else {
2022 				/*
2023 				  If it is a string, what is pointed will be freed
2024 				  later in free_result(). We need to remove the variable to
2025 				  which the user has lost reference.
2026 				*/
2027 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
2028 				ZVAL_NULL(stmt->result_bind[i].zv);
2029 #endif
2030 				zval_ptr_dtor(&stmt->result_bind[i].zv);
2031 			}
2032 		}
2033 	}
2034 	s->m->free_result_bind(s, stmt->result_bind TSRMLS_CC);
2035 	stmt->result_bind = NULL;
2036 
2037 	DBG_VOID_RETURN;
2038 }
2039 /* }}} */
2040 
2041 
2042 /* {{{ mysqlnd_stmt_separate_one_result_bind */
2043 static void
mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s,unsigned int param_no TSRMLS_DC)2044 mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s, unsigned int param_no TSRMLS_DC)
2045 {
2046 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2047 	DBG_ENTER("mysqlnd_stmt_separate_one_result_bind");
2048 	if (!stmt) {
2049 		DBG_VOID_RETURN;
2050 	}
2051 	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);
2052 
2053 	if (!stmt->result_bind) {
2054 		DBG_VOID_RETURN;
2055 	}
2056 
2057 	/*
2058 	  Because only the bound variables can point to our internal buffers, then
2059 	  separate or free only them. Free is possible because the user could have
2060 	  lost reference.
2061 	*/
2062 	/* Let's try with no cache */
2063 	if (stmt->result_bind[param_no].bound == TRUE) {
2064 		DBG_INF_FMT("%u has refcount=%u", param_no, Z_REFCOUNT_P(stmt->result_bind[param_no].zv));
2065 		/*
2066 		  We have to separate the actual zval value of the bound
2067 		  variable from our allocated zvals or we will face double-free
2068 		*/
2069 		if (Z_REFCOUNT_P(stmt->result_bind[param_no].zv) > 1) {
2070 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
2071 			zval_copy_ctor(stmt->result_bind[param_no].zv);
2072 #endif
2073 			zval_ptr_dtor(&stmt->result_bind[param_no].zv);
2074 		} else {
2075 			/*
2076 			  If it is a string, what is pointed will be freed
2077 			  later in free_result(). We need to remove the variable to
2078 			  which the user has lost reference.
2079 			*/
2080 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
2081 			ZVAL_NULL(stmt->result_bind[param_no].zv);
2082 #endif
2083 			zval_ptr_dtor(&stmt->result_bind[param_no].zv);
2084 		}
2085 	}
2086 
2087 	DBG_VOID_RETURN;
2088 }
2089 /* }}} */
2090 
2091 
2092 /* {{{ mysqlnd_stmt::free_stmt_result */
2093 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_result)2094 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)(MYSQLND_STMT * const s TSRMLS_DC)
2095 {
2096 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2097 	DBG_ENTER("mysqlnd_stmt::free_stmt_result");
2098 	if (!stmt) {
2099 		DBG_VOID_RETURN;
2100 	}
2101 
2102 	/*
2103 	  First separate the bound variables, which point to the result set, then
2104 	  destroy the set.
2105 	*/
2106 	mysqlnd_stmt_separate_result_bind(s TSRMLS_CC);
2107 	/* Not every statement has a result set attached */
2108 	if (stmt->result) {
2109 		stmt->result->m.free_result_internal(stmt->result TSRMLS_CC);
2110 		stmt->result = NULL;
2111 	}
2112 	if (stmt->error_info->error_list) {
2113 		zend_llist_clean(stmt->error_info->error_list);
2114 		mnd_pefree(stmt->error_info->error_list, s->persistent);
2115 		stmt->error_info->error_list = NULL;
2116 	}
2117 
2118 	DBG_VOID_RETURN;
2119 }
2120 /* }}} */
2121 
2122 
2123 /* {{{ mysqlnd_stmt::free_stmt_content */
2124 static void
MYSQLND_METHOD(mysqlnd_stmt,free_stmt_content)2125 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content)(MYSQLND_STMT * const s TSRMLS_DC)
2126 {
2127 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2128 	DBG_ENTER("mysqlnd_stmt::free_stmt_content");
2129 	if (!stmt) {
2130 		DBG_VOID_RETURN;
2131 	}
2132 	DBG_INF_FMT("stmt=%lu param_bind=%p param_count=%u", stmt->stmt_id, stmt->param_bind, stmt->param_count);
2133 
2134 	/* Destroy the input bind */
2135 	if (stmt->param_bind) {
2136 		unsigned int i;
2137 		/*
2138 		  Because only the bound variables can point to our internal buffers, then
2139 		  separate or free only them. Free is possible because the user could have
2140 		  lost reference.
2141 		*/
2142 		for (i = 0; i < stmt->param_count; i++) {
2143 			/*
2144 			  If bind_one_parameter was used, but not everything was
2145 			  bound and nothing was fetched, then some `zv` could be NULL
2146 			*/
2147 			if (stmt->param_bind[i].zv) {
2148 				zval_ptr_dtor(&stmt->param_bind[i].zv);
2149 			}
2150 		}
2151 		s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC);
2152 		stmt->param_bind = NULL;
2153 	}
2154 
2155 	s->m->free_stmt_result(s TSRMLS_CC);
2156 	DBG_VOID_RETURN;
2157 }
2158 /* }}} */
2159 
2160 
2161 /* {{{ mysqlnd_stmt::net_close */
2162 static enum_func_status
MYSQLND_METHOD_PRIVATE(mysqlnd_stmt,net_close)2163 MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close)(MYSQLND_STMT * const s, zend_bool implicit TSRMLS_DC)
2164 {
2165 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2166 	MYSQLND_CONN_DATA * conn;
2167 	zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */];
2168 	enum_mysqlnd_collected_stats statistic = STAT_LAST;
2169 
2170 	DBG_ENTER("mysqlnd_stmt::net_close");
2171 	if (!stmt || !stmt->conn) {
2172 		DBG_RETURN(FAIL);
2173 	}
2174 	DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
2175 
2176 	conn = stmt->conn;
2177 
2178 	SET_EMPTY_ERROR(*stmt->error_info);
2179 	SET_EMPTY_ERROR(*stmt->conn->error_info);
2180 
2181 	/*
2182 	  If the user decided to close the statement right after execute()
2183 	  We have to call the appropriate use_result() or store_result() and
2184 	  clean.
2185 	*/
2186 	do {
2187 		if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
2188 			DBG_INF("fetching result set header");
2189 			stmt->default_rset_handler(s TSRMLS_CC);
2190 			stmt->state = MYSQLND_STMT_USER_FETCHING;
2191 		}
2192 
2193 		/* unbuffered set not fetched to the end ? Clean the line */
2194 		if (stmt->result) {
2195 			DBG_INF("skipping result");
2196 			stmt->result->m.skip_result(stmt->result TSRMLS_CC);
2197 		}
2198 	} while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
2199 	/*
2200 	  After this point we are allowed to free the result set,
2201 	  as we have cleaned the line
2202 	*/
2203 	if (stmt->stmt_id) {
2204 		MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE?	STAT_FREE_RESULT_IMPLICIT:
2205 														STAT_FREE_RESULT_EXPLICIT);
2206 
2207 		int4store(cmd_buf, stmt->stmt_id);
2208 		if (CONN_GET_STATE(conn) == CONN_READY &&
2209 			FAIL == conn->m->simple_command(conn, COM_STMT_CLOSE, cmd_buf, sizeof(cmd_buf),
2210 										   PROT_LAST /* COM_STMT_CLOSE doesn't send an OK packet*/,
2211 										   FALSE, TRUE TSRMLS_CC)) {
2212 			COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info);
2213 			DBG_RETURN(FAIL);
2214 		}
2215 	}
2216 	switch (stmt->execute_count) {
2217 		case 0:
2218 			statistic = STAT_PS_PREPARED_NEVER_EXECUTED;
2219 			break;
2220 		case 1:
2221 			statistic = STAT_PS_PREPARED_ONCE_USED;
2222 			break;
2223 		default:
2224 			break;
2225 	}
2226 	if (statistic != STAT_LAST) {
2227 		MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
2228 	}
2229 
2230 	if (stmt->execute_cmd_buffer.buffer) {
2231 		mnd_pefree(stmt->execute_cmd_buffer.buffer, stmt->persistent);
2232 		stmt->execute_cmd_buffer.buffer = NULL;
2233 	}
2234 
2235 	s->m->free_stmt_content(s TSRMLS_CC);
2236 
2237 	if (stmt->conn) {
2238 		stmt->conn->m->free_reference(stmt->conn TSRMLS_CC);
2239 		stmt->conn = NULL;
2240 	}
2241 
2242 	DBG_RETURN(PASS);
2243 }
2244 /* }}} */
2245 
2246 /* {{{ mysqlnd_stmt::dtor */
2247 static enum_func_status
MYSQLND_METHOD(mysqlnd_stmt,dtor)2248 MYSQLND_METHOD(mysqlnd_stmt, dtor)(MYSQLND_STMT * const s, zend_bool implicit TSRMLS_DC)
2249 {
2250 	MYSQLND_STMT_DATA * stmt = (s != NULL) ? s->data:NULL;
2251 	enum_func_status ret = FAIL;
2252 	zend_bool persistent = (s != NULL) ? s->persistent : 0;
2253 
2254 	DBG_ENTER("mysqlnd_stmt::dtor");
2255 	if (stmt) {
2256 		DBG_INF_FMT("stmt=%p", stmt);
2257 
2258 		MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE?	STAT_STMT_CLOSE_IMPLICIT:
2259 														STAT_STMT_CLOSE_EXPLICIT);
2260 
2261 		ret = s->m->net_close(s, implicit TSRMLS_CC);
2262 		mnd_pefree(stmt, persistent);
2263 	}
2264 	mnd_pefree(s, persistent);
2265 
2266 	DBG_INF(ret == PASS? "PASS":"FAIL");
2267 	DBG_RETURN(ret);
2268 }
2269 /* }}} */
2270 
2271 
2272 /* {{{ mysqlnd_stmt::alloc_param_bind */
2273 static MYSQLND_PARAM_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_param_bind)2274 MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind)(MYSQLND_STMT * const s TSRMLS_DC)
2275 {
2276 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2277 	DBG_ENTER("mysqlnd_stmt::alloc_param_bind");
2278 	if (!stmt) {
2279 		DBG_RETURN(NULL);
2280 	}
2281 	DBG_RETURN(mnd_pecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND), stmt->persistent));
2282 }
2283 /* }}} */
2284 
2285 
2286 /* {{{ mysqlnd_stmt::alloc_result_bind */
2287 static MYSQLND_RESULT_BIND *
MYSQLND_METHOD(mysqlnd_stmt,alloc_result_bind)2288 MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind)(MYSQLND_STMT * const s TSRMLS_DC)
2289 {
2290 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2291 	DBG_ENTER("mysqlnd_stmt::alloc_result_bind");
2292 	if (!stmt) {
2293 		DBG_RETURN(NULL);
2294 	}
2295 	DBG_RETURN(mnd_pecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND), stmt->persistent));
2296 }
2297 /* }}} */
2298 
2299 
2300 /* {{{ param_bind::free_parameter_bind */
2301 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_parameter_bind)2302 MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * param_bind TSRMLS_DC)
2303 {
2304 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2305 	if (stmt) {
2306 		mnd_pefree(param_bind, stmt->persistent);
2307 	}
2308 }
2309 /* }}} */
2310 
2311 
2312 /* {{{ mysqlnd_stmt::free_result_bind */
2313 PHPAPI void
MYSQLND_METHOD(mysqlnd_stmt,free_result_bind)2314 MYSQLND_METHOD(mysqlnd_stmt, free_result_bind)(MYSQLND_STMT * const s, MYSQLND_RESULT_BIND * result_bind TSRMLS_DC)
2315 {
2316 	MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
2317 	if (stmt) {
2318 		mnd_pefree(result_bind, stmt->persistent);
2319 	}
2320 }
2321 /* }}} */
2322 
2323 
2324 
2325 MYSQLND_CLASS_METHODS_START(mysqlnd_stmt)
2326 	MYSQLND_METHOD(mysqlnd_stmt, prepare),
2327 	MYSQLND_METHOD(mysqlnd_stmt, execute),
2328 	MYSQLND_METHOD(mysqlnd_stmt, use_result),
2329 	MYSQLND_METHOD(mysqlnd_stmt, store_result),
2330 	MYSQLND_METHOD(mysqlnd_stmt, get_result),
2331 	MYSQLND_METHOD(mysqlnd_stmt, more_results),
2332 	MYSQLND_METHOD(mysqlnd_stmt, next_result),
2333 	MYSQLND_METHOD(mysqlnd_stmt, free_result),
2334 	MYSQLND_METHOD(mysqlnd_stmt, data_seek),
2335 	MYSQLND_METHOD(mysqlnd_stmt, reset),
2336 	MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close),
2337 	MYSQLND_METHOD(mysqlnd_stmt, dtor),
2338 
2339 	MYSQLND_METHOD(mysqlnd_stmt, fetch),
2340 
2341 	MYSQLND_METHOD(mysqlnd_stmt, bind_parameters),
2342 	MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter),
2343 	MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param),
2344 	MYSQLND_METHOD(mysqlnd_stmt, bind_result),
2345 	MYSQLND_METHOD(mysqlnd_stmt, bind_one_result),
2346 	MYSQLND_METHOD(mysqlnd_stmt, send_long_data),
2347 	MYSQLND_METHOD(mysqlnd_stmt, param_metadata),
2348 	MYSQLND_METHOD(mysqlnd_stmt, result_metadata),
2349 
2350 	MYSQLND_METHOD(mysqlnd_stmt, insert_id),
2351 	MYSQLND_METHOD(mysqlnd_stmt, affected_rows),
2352 	MYSQLND_METHOD(mysqlnd_stmt, num_rows),
2353 
2354 	MYSQLND_METHOD(mysqlnd_stmt, param_count),
2355 	MYSQLND_METHOD(mysqlnd_stmt, field_count),
2356 	MYSQLND_METHOD(mysqlnd_stmt, warning_count),
2357 
2358 	MYSQLND_METHOD(mysqlnd_stmt, errno),
2359 	MYSQLND_METHOD(mysqlnd_stmt, error),
2360 	MYSQLND_METHOD(mysqlnd_stmt, sqlstate),
2361 
2362 	MYSQLND_METHOD(mysqlnd_stmt, attr_get),
2363 	MYSQLND_METHOD(mysqlnd_stmt, attr_set),
2364 
2365 
2366 	MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind),
2367 	MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind),
2368 	MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind),
2369 	MYSQLND_METHOD(mysqlnd_stmt, free_result_bind),
2370 	MYSQLND_METHOD(mysqlnd_stmt, server_status),
2371 	mysqlnd_stmt_execute_generate_request,
2372 	mysqlnd_stmt_execute_parse_response,
2373 	MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content),
2374 	MYSQLND_METHOD(mysqlnd_stmt, flush),
2375 	MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)
2376 MYSQLND_CLASS_METHODS_END;
2377 
2378 
2379 /* {{{ _mysqlnd_stmt_init */
2380 MYSQLND_STMT *
_mysqlnd_stmt_init(MYSQLND_CONN_DATA * const conn TSRMLS_DC)2381 _mysqlnd_stmt_init(MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2382 {
2383 	MYSQLND_STMT * ret;
2384 	DBG_ENTER("_mysqlnd_stmt_init");
2385 	ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_prepared_statement(conn TSRMLS_CC);
2386 	DBG_RETURN(ret);
2387 }
2388 /* }}} */
2389 
2390 
2391 /* {{{ _mysqlnd_init_ps_subsystem */
_mysqlnd_init_ps_subsystem()2392 void _mysqlnd_init_ps_subsystem()
2393 {
2394 	mysqlnd_stmt_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_stmt));
2395 	_mysqlnd_init_ps_fetch_subsystem();
2396 }
2397 /* }}} */
2398 
2399 
2400 /*
2401  * Local variables:
2402  * tab-width: 4
2403  * c-basic-offset: 4
2404  * End:
2405  * vim600: noet sw=4 ts=4 fdm=marker
2406  * vim<600: noet sw=4 ts=4
2407  */
2408