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