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