xref: /PHP-5.4/ext/mysqlnd/mysqlnd_result.c (revision 7efbd70b)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2006-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Andrey Hristov <andrey@mysql.com>                           |
16   |          Ulf Wendel <uwendel@mysql.com>                              |
17   |          Georg Richter <georg@mysql.com>                             |
18   +----------------------------------------------------------------------+
19 */
20 
21 /* $Id$ */
22 #include "php.h"
23 #include "mysqlnd.h"
24 #include "mysqlnd_wireprotocol.h"
25 #include "mysqlnd_block_alloc.h"
26 #include "mysqlnd_priv.h"
27 #include "mysqlnd_result.h"
28 #include "mysqlnd_result_meta.h"
29 #include "mysqlnd_statistics.h"
30 #include "mysqlnd_debug.h"
31 #include "mysqlnd_ext_plugin.h"
32 
33 #define MYSQLND_SILENT
34 
35 
36 /* {{{ mysqlnd_res::initialize_result_set_rest */
37 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,initialize_result_set_rest)38 MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest)(MYSQLND_RES * const result TSRMLS_DC)
39 {
40 	unsigned int i;
41 	zval **data_cursor = result->stored_data? result->stored_data->data:NULL;
42 	zval **data_begin = result->stored_data? result->stored_data->data:NULL;
43 	unsigned int field_count = result->meta? result->meta->field_count : 0;
44 	uint64_t row_count = result->stored_data? result->stored_data->row_count:0;
45 	enum_func_status ret = PASS;
46 	DBG_ENTER("mysqlnd_res::initialize_result_set_rest");
47 
48 	if (!data_cursor || row_count == result->stored_data->initialized_rows) {
49 		DBG_RETURN(ret);
50 	}
51 	while ((data_cursor - data_begin) < (int)(row_count * field_count)) {
52 		if (NULL == data_cursor[0]) {
53 			enum_func_status rc = result->m.row_decoder(
54 									result->stored_data->row_buffers[(data_cursor - data_begin) / field_count],
55 									data_cursor,
56 									result->meta->field_count,
57 									result->meta->fields,
58 									result->conn->options->numeric_and_datetime_as_unicode,
59 									result->conn->options->int_and_float_native,
60 									result->conn->stats TSRMLS_CC);
61 			if (rc != PASS) {
62 				ret = FAIL;
63 				break;
64 			}
65 			result->stored_data->initialized_rows++;
66 			for (i = 0; i < result->field_count; i++) {
67 				/*
68 				  NULL fields are 0 length, 0 is not more than 0
69 				  String of zero size, definitely can't be the next max_length.
70 				  Thus for NULL and zero-length we are quite efficient.
71 				*/
72 				if (Z_TYPE_P(data_cursor[i]) >= IS_STRING) {
73 					unsigned long len = Z_STRLEN_P(data_cursor[i]);
74 					if (result->meta->fields[i].max_length < len) {
75 						result->meta->fields[i].max_length = len;
76 					}
77 				}
78 			}
79 		}
80 		data_cursor += field_count;
81 	}
82 	DBG_RETURN(ret);
83 }
84 /* }}} */
85 
86 
87 /* {{{ mysqlnd_rset_zval_ptr_dtor */
88 static void
mysqlnd_rset_zval_ptr_dtor(zval ** zv,enum_mysqlnd_res_type type,zend_bool * copy_ctor_called TSRMLS_DC)89 mysqlnd_rset_zval_ptr_dtor(zval **zv, enum_mysqlnd_res_type type, zend_bool * copy_ctor_called TSRMLS_DC)
90 {
91 	DBG_ENTER("mysqlnd_rset_zval_ptr_dtor");
92 	if (!zv || !*zv) {
93 		*copy_ctor_called = FALSE;
94 		DBG_ERR_FMT("zv was NULL");
95 		DBG_VOID_RETURN;
96 	}
97 	/*
98 	  This zval is not from the cache block.
99 	  Thus the refcount is -1 than of a zval from the cache,
100 	  because the zvals from the cache are owned by it.
101 	*/
102 	if (type == MYSQLND_RES_PS_BUF || type == MYSQLND_RES_PS_UNBUF) {
103 		*copy_ctor_called = FALSE;
104 		; /* do nothing, zval_ptr_dtor will do the job*/
105 	} else if (Z_REFCOUNT_PP(zv) > 1) {
106 		/*
107 		  Not a prepared statement, then we have to
108 		  call copy_ctor and then zval_ptr_dtor()
109 
110 		  In Unicode mode the destruction  of the zvals should not call
111 		  zval_copy_ctor() because then we will leak.
112 		  I suppose we can use UG(unicode) in mysqlnd.c when freeing a result set
113 		  to check if we need to call copy_ctor().
114 
115 		  If the type is IS_UNICODE, which can happen with PHP6, then we don't
116 		  need to copy_ctor, as the data doesn't point to our internal buffers.
117 		  If it's string (in PHP5 always) and in PHP6 if data is binary, then
118 		  it still points to internal buffers and has to be copied.
119 		*/
120 		if (Z_TYPE_PP(zv) == IS_STRING) {
121 			zval_copy_ctor(*zv);
122 		}
123 		*copy_ctor_called = TRUE;
124 	} else {
125 		/*
126 		  noone but us point to this, so we can safely ZVAL_NULL the zval,
127 		  so Zend does not try to free what the zval points to - which is
128 		  in result set buffers
129 		*/
130 		*copy_ctor_called = FALSE;
131 		if (Z_TYPE_PP(zv) == IS_STRING) {
132 			ZVAL_NULL(*zv);
133 		}
134 	}
135 	zval_ptr_dtor(zv);
136 	DBG_VOID_RETURN;
137 }
138 /* }}} */
139 
140 
141 /* {{{ mysqlnd_res::unbuffered_free_last_data */
142 static void
MYSQLND_METHOD(mysqlnd_res,unbuffered_free_last_data)143 MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data)(MYSQLND_RES * result TSRMLS_DC)
144 {
145 	MYSQLND_RES_UNBUFFERED *unbuf = result->unbuf;
146 
147 	DBG_ENTER("mysqlnd_res::unbuffered_free_last_data");
148 
149 	if (!unbuf) {
150 		DBG_VOID_RETURN;
151 	}
152 
153 	if (unbuf->last_row_data) {
154 		unsigned int i, ctor_called_count = 0;
155 		zend_bool copy_ctor_called;
156 		MYSQLND_STATS *global_stats = result->conn? result->conn->stats:NULL;
157 
158 		for (i = 0; i < result->field_count; i++) {
159 			mysqlnd_rset_zval_ptr_dtor(&(unbuf->last_row_data[i]), result->type, &copy_ctor_called TSRMLS_CC);
160 			if (copy_ctor_called) {
161 				++ctor_called_count;
162 			}
163 		}
164 		DBG_INF_FMT("copy_ctor_called_count=%u", ctor_called_count);
165 		/* By using value3 macros we hold a mutex only once, there is no value2 */
166 		MYSQLND_INC_CONN_STATISTIC_W_VALUE2(global_stats,
167 											STAT_COPY_ON_WRITE_PERFORMED,
168 											ctor_called_count,
169 											STAT_COPY_ON_WRITE_SAVED,
170 											result->field_count - ctor_called_count);
171 		/* Free last row's zvals */
172 		mnd_efree(unbuf->last_row_data);
173 		unbuf->last_row_data = NULL;
174 	}
175 	if (unbuf->last_row_buffer) {
176 		DBG_INF("Freeing last row buffer");
177 		/* Nothing points to this buffer now, free it */
178 		unbuf->last_row_buffer->free_chunk(unbuf->last_row_buffer TSRMLS_CC);
179 		unbuf->last_row_buffer = NULL;
180 	}
181 
182 	DBG_VOID_RETURN;
183 }
184 /* }}} */
185 
186 
187 /* {{{ mysqlnd_res::free_buffered_data */
188 static void
MYSQLND_METHOD(mysqlnd_res,free_buffered_data)189 MYSQLND_METHOD(mysqlnd_res, free_buffered_data)(MYSQLND_RES * result TSRMLS_DC)
190 {
191 	MYSQLND_RES_BUFFERED *set = result->stored_data;
192 	unsigned int field_count = result->field_count;
193 	int64_t row;
194 
195 	DBG_ENTER("mysqlnd_res::free_buffered_data");
196 	DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count);
197 
198 	if (set->data) {
199 		unsigned int copy_on_write_performed = 0;
200 		unsigned int copy_on_write_saved = 0;
201 		zval **data = set->data;
202 		set->data = NULL; /* prevent double free if following loop is interrupted */
203 
204 		for (row = set->row_count - 1; row >= 0; row--) {
205 			zval **current_row = data + row * field_count;
206 			MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row];
207 			int64_t col;
208 
209 			if (current_row != NULL) {
210 				for (col = field_count - 1; col >= 0; --col) {
211 					if (current_row[col]) {
212 						zend_bool copy_ctor_called;
213 						mysqlnd_rset_zval_ptr_dtor(&(current_row[col]), result->type, &copy_ctor_called TSRMLS_CC);
214 						if (copy_ctor_called) {
215 							++copy_on_write_performed;
216 						} else {
217 							++copy_on_write_saved;
218 						}
219 					}
220 				}
221 			}
222 			current_buffer->free_chunk(current_buffer TSRMLS_CC);
223 		}
224 
225 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_COPY_ON_WRITE_PERFORMED, copy_on_write_performed,
226 											  STAT_COPY_ON_WRITE_SAVED, copy_on_write_saved);
227 		mnd_efree(data);
228 	}
229 
230 	if (set->row_buffers) {
231 		mnd_efree(set->row_buffers);
232 		set->row_buffers	= NULL;
233 	}
234 	set->data_cursor = NULL;
235 	set->row_count	= 0;
236 
237 	mnd_efree(set);
238 
239 	DBG_VOID_RETURN;
240 }
241 /* }}} */
242 
243 
244 /* {{{ mysqlnd_res::free_result_buffers */
245 static void
MYSQLND_METHOD(mysqlnd_res,free_result_buffers)246 MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES * result TSRMLS_DC)
247 {
248 	DBG_ENTER("mysqlnd_res::free_result_buffers");
249 	DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown"));
250 
251 	if (result->unbuf) {
252 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
253 		mnd_efree(result->unbuf);
254 		result->unbuf = NULL;
255 	} else if (result->stored_data) {
256 		result->m.free_buffered_data(result TSRMLS_CC);
257 		result->stored_data = NULL;
258 	}
259 
260 	if (result->lengths) {
261 		mnd_efree(result->lengths);
262 		result->lengths = NULL;
263 	}
264 
265 	if (result->row_packet) {
266 		PACKET_FREE(result->row_packet);
267 		result->row_packet = NULL;
268 	}
269 
270 	if (result->result_set_memory_pool) {
271 		mysqlnd_mempool_destroy(result->result_set_memory_pool TSRMLS_CC);
272 		result->result_set_memory_pool = NULL;
273 	}
274 
275 	DBG_VOID_RETURN;
276 }
277 /* }}} */
278 
279 
280 /* {{{ mysqlnd_internal_free_result_contents */
281 static
mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC)282 void mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC)
283 {
284 	DBG_ENTER("mysqlnd_internal_free_result_contents");
285 
286 	result->m.free_result_buffers(result TSRMLS_CC);
287 
288 	if (result->meta) {
289 		result->meta->m->free_metadata(result->meta TSRMLS_CC);
290 		result->meta = NULL;
291 	}
292 
293 	DBG_VOID_RETURN;
294 }
295 /* }}} */
296 
297 
298 /* {{{ mysqlnd_internal_free_result */
299 static
mysqlnd_internal_free_result(MYSQLND_RES * result TSRMLS_DC)300 void mysqlnd_internal_free_result(MYSQLND_RES * result TSRMLS_DC)
301 {
302 	DBG_ENTER("mysqlnd_internal_free_result");
303 	result->m.free_result_contents(result TSRMLS_CC);
304 
305 	if (result->conn) {
306 		result->conn->m->free_reference(result->conn TSRMLS_CC);
307 		result->conn = NULL;
308 	}
309 
310 	mnd_pefree(result, result->persistent);
311 
312 	DBG_VOID_RETURN;
313 }
314 /* }}} */
315 
316 
317 /* {{{ mysqlnd_res::read_result_metadata */
318 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,read_result_metadata)319 MYSQLND_METHOD(mysqlnd_res, read_result_metadata)(MYSQLND_RES * result, MYSQLND_CONN_DATA * conn TSRMLS_DC)
320 {
321 	DBG_ENTER("mysqlnd_res::read_result_metadata");
322 
323 	/*
324 	  Make it safe to call it repeatedly for PS -
325 	  better free and allocate a new because the number of field might change
326 	  (select *) with altered table. Also for statements which skip the PS
327 	  infrastructure!
328 	*/
329 	if (result->meta) {
330 		result->meta->m->free_metadata(result->meta TSRMLS_CC);
331 		result->meta = NULL;
332 	}
333 
334 	result->meta = result->m.result_meta_init(result->field_count, result->persistent TSRMLS_CC);
335 	if (!result->meta) {
336 		SET_OOM_ERROR(*conn->error_info);
337 		DBG_RETURN(FAIL);
338 	}
339 
340 	/* 1. Read all fields metadata */
341 
342 	/* It's safe to reread without freeing */
343 	if (FAIL == result->meta->m->read_metadata(result->meta, conn TSRMLS_CC)) {
344 		result->m.free_result_contents(result TSRMLS_CC);
345 		DBG_RETURN(FAIL);
346 	}
347 	/* COM_FIELD_LIST is broken and has premature EOF, thus we need to hack here and in mysqlnd_res_meta.c */
348 	result->field_count = result->meta->field_count;
349 
350 	/*
351 	  2. Follows an EOF packet, which the client of mysqlnd_read_result_metadata()
352 	     should consume.
353 	  3. If there is a result set, it follows. The last packet will have 'eof' set
354 	     If PS, then no result set follows.
355 	*/
356 
357 	DBG_RETURN(PASS);
358 }
359 /* }}} */
360 
361 
362 /* {{{ mysqlnd_query_read_result_set_header */
363 enum_func_status
mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn,MYSQLND_STMT * s TSRMLS_DC)364 mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s TSRMLS_DC)
365 {
366 	MYSQLND_STMT_DATA * stmt = s ? s->data:NULL;
367 	enum_func_status ret;
368 	MYSQLND_PACKET_RSET_HEADER * rset_header = NULL;
369 	MYSQLND_PACKET_EOF * fields_eof = NULL;
370 
371 	DBG_ENTER("mysqlnd_query_read_result_set_header");
372 	DBG_INF_FMT("stmt=%lu", stmt? stmt->stmt_id:0);
373 
374 	ret = FAIL;
375 	do {
376 		rset_header = conn->protocol->m.get_rset_header_packet(conn->protocol, FALSE TSRMLS_CC);
377 		if (!rset_header) {
378 			SET_OOM_ERROR(*conn->error_info);
379 			ret = FAIL;
380 			break;
381 		}
382 
383 		SET_ERROR_AFF_ROWS(conn);
384 
385 		if (FAIL == (ret = PACKET_READ(rset_header, conn))) {
386 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error reading result set's header");
387 			break;
388 		}
389 
390 		if (rset_header->error_info.error_no) {
391 			/*
392 			  Cover a protocol design error: error packet does not
393 			  contain the server status. Therefore, the client has no way
394 			  to find out whether there are more result sets of
395 			  a multiple-result-set statement pending. Luckily, in 5.0 an
396 			  error always aborts execution of a statement, wherever it is
397 			  a multi-statement or a stored procedure, so it should be
398 			  safe to unconditionally turn off the flag here.
399 			*/
400 			conn->upsert_status->server_status &= ~SERVER_MORE_RESULTS_EXISTS;
401 			/*
402 			  This will copy the error code and the messages, as they
403 			  are buffers in the struct
404 			*/
405 			COPY_CLIENT_ERROR(*conn->error_info, rset_header->error_info);
406 			ret = FAIL;
407 			DBG_ERR_FMT("error=%s", rset_header->error_info.error);
408 			/* Return back from CONN_QUERY_SENT */
409 			CONN_SET_STATE(conn, CONN_READY);
410 			break;
411 		}
412 		conn->error_info->error_no = 0;
413 
414 		switch (rset_header->field_count) {
415 			case MYSQLND_NULL_LENGTH: {	/* LOAD DATA LOCAL INFILE */
416 				zend_bool is_warning;
417 				DBG_INF("LOAD DATA");
418 				conn->last_query_type = QUERY_LOAD_LOCAL;
419 				conn->field_count = 0; /* overwrite previous value, or the last value could be used and lead to bug#53503 */
420 				CONN_SET_STATE(conn, CONN_SENDING_LOAD_DATA);
421 				ret = mysqlnd_handle_local_infile(conn, rset_header->info_or_local_file, &is_warning TSRMLS_CC);
422 				CONN_SET_STATE(conn,  (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT);
423 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
424 				break;
425 			}
426 			case 0:				/* UPSERT */
427 				DBG_INF("UPSERT");
428 				conn->last_query_type = QUERY_UPSERT;
429 				conn->field_count = rset_header->field_count;
430 				memset(conn->upsert_status, 0, sizeof(*conn->upsert_status));
431 				conn->upsert_status->warning_count = rset_header->warning_count;
432 				conn->upsert_status->server_status = rset_header->server_status;
433 				conn->upsert_status->affected_rows = rset_header->affected_rows;
434 				conn->upsert_status->last_insert_id = rset_header->last_insert_id;
435 				SET_NEW_MESSAGE(conn->last_message, conn->last_message_len,
436 								rset_header->info_or_local_file, rset_header->info_or_local_file_len,
437 								conn->persistent);
438 				/* Result set can follow UPSERT statement, check server_status */
439 				if (conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
440 					CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
441 				} else {
442 					CONN_SET_STATE(conn, CONN_READY);
443 				}
444 				ret = PASS;
445 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
446 				break;
447 			default: do {			/* Result set */
448 				MYSQLND_RES * result;
449 				enum_mysqlnd_collected_stats statistic = STAT_LAST;
450 
451 				DBG_INF("Result set pending");
452 				SET_EMPTY_MESSAGE(conn->last_message, conn->last_message_len, conn->persistent);
453 
454 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_RSET_QUERY);
455 				memset(conn->upsert_status, 0, sizeof(*conn->upsert_status));
456 				/* restore after zeroing */
457 				SET_ERROR_AFF_ROWS(conn);
458 
459 				conn->last_query_type = QUERY_SELECT;
460 				CONN_SET_STATE(conn, CONN_FETCHING_DATA);
461 				/* PS has already allocated it */
462 				conn->field_count = rset_header->field_count;
463 				if (!stmt) {
464 					result = conn->current_result = conn->m->result_init(rset_header->field_count, conn->persistent TSRMLS_CC);
465 				} else {
466 					if (!stmt->result) {
467 						DBG_INF("This is 'SHOW'/'EXPLAIN'-like query.");
468 						/*
469 						  This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
470 						  prepared statements can't send result set metadata for these queries
471 						  on prepare stage. Read it now.
472 						*/
473 						result = stmt->result = conn->m->result_init(rset_header->field_count, stmt->persistent TSRMLS_CC);
474 					} else {
475 						/*
476 						  Update result set metadata if it for some reason changed between
477 						  prepare and execute, i.e.:
478 						  - in case of 'SELECT ?' we don't know column type unless data was
479 							supplied to mysql_stmt_execute, so updated column type is sent
480 							now.
481 						  - if data dictionary changed between prepare and execute, for
482 							example a table used in the query was altered.
483 						  Note, that now (4.1.3) we always send metadata in reply to
484 						  COM_STMT_EXECUTE (even if it is not necessary), so either this or
485 						  previous branch always works.
486 						*/
487 					}
488 					result = stmt->result;
489 				}
490 				if (!result) {
491 					SET_OOM_ERROR(*conn->error_info);
492 					ret = FAIL;
493 					break;
494 				}
495 
496 				if (FAIL == (ret = result->m.read_result_metadata(result, conn TSRMLS_CC))) {
497 					/* For PS, we leave them in Prepared state */
498 					if (!stmt && conn->current_result) {
499 						mnd_efree(conn->current_result);
500 						conn->current_result = NULL;
501 					}
502 					DBG_ERR("Error occurred while reading metadata");
503 					break;
504 				}
505 
506 				/* Check for SERVER_STATUS_MORE_RESULTS if needed */
507 				fields_eof = conn->protocol->m.get_eof_packet(conn->protocol, FALSE TSRMLS_CC);
508 				if (!fields_eof) {
509 					SET_OOM_ERROR(*conn->error_info);
510 					ret = FAIL;
511 					break;
512 				}
513 				if (FAIL == (ret = PACKET_READ(fields_eof, conn))) {
514 					DBG_ERR("Error occurred while reading the EOF packet");
515 					result->m.free_result_contents(result TSRMLS_CC);
516 					mnd_efree(result);
517 					if (!stmt) {
518 						conn->current_result = NULL;
519 					} else {
520 						stmt->result = NULL;
521 						memset(stmt, 0, sizeof(*stmt));
522 						stmt->state = MYSQLND_STMT_INITTED;
523 					}
524 				} else {
525 					unsigned int to_log = MYSQLND_G(log_mask);
526 					to_log &= fields_eof->server_status;
527 					DBG_INF_FMT("warnings=%u server_status=%u", fields_eof->warning_count, fields_eof->server_status);
528 					conn->upsert_status->warning_count = fields_eof->warning_count;
529 					/*
530 					  If SERVER_MORE_RESULTS_EXISTS is set then this is either MULTI_QUERY or a CALL()
531 					  The first packet after sending the query/com_execute has the bit set only
532 					  in this cases. Not sure why it's a needed but it marks that the whole stream
533 					  will include many result sets. What actually matters are the bits set at the end
534 					  of every result set (the EOF packet).
535 					*/
536 					conn->upsert_status->server_status = fields_eof->server_status;
537 					if (fields_eof->server_status & SERVER_QUERY_NO_GOOD_INDEX_USED) {
538 						statistic = STAT_BAD_INDEX_USED;
539 					} else if (fields_eof->server_status & SERVER_QUERY_NO_INDEX_USED) {
540 						statistic = STAT_NO_INDEX_USED;
541 					} else if (fields_eof->server_status & SERVER_QUERY_WAS_SLOW) {
542 						statistic = STAT_QUERY_WAS_SLOW;
543 					}
544 					if (to_log) {
545 #if A0
546 						char *backtrace = mysqlnd_get_backtrace(TSRMLS_C);
547 						php_log_err(backtrace TSRMLS_CC);
548 						efree(backtrace);
549 #endif
550 					}
551 					MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
552 				}
553 			} while (0);
554 			PACKET_FREE(fields_eof);
555 			break; /* switch break */
556 		}
557 	} while (0);
558 	PACKET_FREE(rset_header);
559 
560 	DBG_INF(ret == PASS? "PASS":"FAIL");
561 	DBG_RETURN(ret);
562 }
563 /* }}} */
564 
565 
566 /* {{{ mysqlnd_fetch_lengths_buffered */
567 /*
568   Do lazy initialization for buffered results. As PHP strings have
569   length inside, this function makes not much sense in the context
570   of PHP, to be called as separate function. But let's have it for
571   completeness.
572 */
573 static unsigned long *
mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result TSRMLS_DC)574 mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result TSRMLS_DC)
575 {
576 	unsigned int i;
577 	zval **previous_row;
578 	MYSQLND_RES_BUFFERED *set = result->stored_data;
579 
580 	/*
581 	  If:
582 	  - unbuffered result
583 	  - first row has not been read
584 	  - last_row has been read
585 	*/
586 	if (set->data_cursor == NULL ||
587 		set->data_cursor == set->data ||
588 		((set->data_cursor - set->data) > (set->row_count * result->meta->field_count) ))
589 	{
590 		return NULL;/* No rows or no more rows */
591 	}
592 
593 	previous_row = set->data_cursor - result->meta->field_count;
594 	for (i = 0; i < result->meta->field_count; i++) {
595 		result->lengths[i] = (Z_TYPE_P(previous_row[i]) == IS_NULL)? 0:Z_STRLEN_P(previous_row[i]);
596 	}
597 
598 	return result->lengths;
599 }
600 /* }}} */
601 
602 
603 /* {{{ mysqlnd_fetch_lengths_unbuffered */
604 static unsigned long *
mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result TSRMLS_DC)605 mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result TSRMLS_DC)
606 {
607 	/* simulate output of libmysql */
608 	return (!result->unbuf || result->unbuf->last_row_data || result->unbuf->eof_reached)? result->lengths:NULL;
609 }
610 /* }}} */
611 
612 
613 /* {{{ mysqlnd_res::fetch_lengths */
_mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC)614 PHPAPI unsigned long * _mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC)
615 {
616 	return result->m.fetch_lengths? result->m.fetch_lengths(result TSRMLS_CC) : NULL;
617 }
618 /* }}} */
619 
620 
621 /* {{{ mysqlnd_fetch_row_unbuffered_c */
622 static MYSQLND_ROW_C
mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC)623 mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC)
624 {
625 	enum_func_status	ret;
626 	MYSQLND_ROW_C		retrow = NULL;
627 	unsigned int		i,
628 						field_count = result->field_count;
629 	MYSQLND_PACKET_ROW	*row_packet = result->row_packet;
630 	unsigned long		*lengths = result->lengths;
631 
632 	DBG_ENTER("mysqlnd_fetch_row_unbuffered_c");
633 
634 	if (result->unbuf->eof_reached) {
635 		/* No more rows obviously */
636 		DBG_RETURN(retrow);
637 	}
638 	if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
639 		SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
640 						 UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
641 		DBG_RETURN(retrow);
642 	}
643 	if (!row_packet) {
644 		/* Not fully initialized object that is being cleaned up */
645 		DBG_RETURN(retrow);
646 	}
647 	/* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
648 	row_packet->skip_extraction = FALSE;
649 
650 	/*
651 	  If we skip rows (row == NULL) we have to
652 	  result->m.unbuffered_free_last_data() before it. The function returns always true.
653 	*/
654 	if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
655 		result->unbuf->row_count++;
656 
657 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
658 
659 		result->unbuf->last_row_data = row_packet->fields;
660 		result->unbuf->last_row_buffer = row_packet->row_buffer;
661 		row_packet->fields = NULL;
662 		row_packet->row_buffer = NULL;
663 
664 		MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
665 
666 		if (!row_packet->skip_extraction) {
667 			MYSQLND_FIELD *field = result->meta->fields;
668 			struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
669 
670 			enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer,
671 										  result->unbuf->last_row_data,
672 										  row_packet->field_count,
673 										  row_packet->fields_metadata,
674 										  result->conn->options->numeric_and_datetime_as_unicode,
675 										  result->conn->options->int_and_float_native,
676 										  result->conn->stats TSRMLS_CC);
677 			if (PASS != rc) {
678 				DBG_RETURN(retrow);
679 			}
680 
681 			retrow = mnd_malloc(result->field_count * sizeof(char *));
682 			if (retrow) {
683 				for (i = 0; i < field_count; i++, field++, hash_key++) {
684 					zval *data = result->unbuf->last_row_data[i];
685 					unsigned int len;
686 
687 					if (Z_TYPE_P(data) != IS_NULL) {
688 						convert_to_string(data);
689 						retrow[i] = Z_STRVAL_P(data);
690 						len = Z_STRLEN_P(data);
691 					} else {
692 						retrow[i] = NULL;
693 						len = 0;
694 					}
695 
696 					if (lengths) {
697 						lengths[i] = len;
698 					}
699 
700 					if (field->max_length < len) {
701 						field->max_length = len;
702 					}
703 				}
704 			} else {
705 				SET_OOM_ERROR(*result->conn->error_info);
706 			}
707 		}
708 	} else if (ret == FAIL) {
709 		if (row_packet->error_info.error_no) {
710 			COPY_CLIENT_ERROR(*result->conn->error_info, row_packet->error_info);
711 			DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
712 		}
713 		CONN_SET_STATE(result->conn, CONN_READY);
714 		result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
715 	} else if (row_packet->eof) {
716 		/* Mark the connection as usable again */
717 		DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
718 		result->unbuf->eof_reached = TRUE;
719 		memset(result->conn->upsert_status, 0, sizeof(*result->conn->upsert_status));
720 		result->conn->upsert_status->warning_count = row_packet->warning_count;
721 		result->conn->upsert_status->server_status = row_packet->server_status;
722 		/*
723 		  result->row_packet will be cleaned when
724 		  destroying the result object
725 		*/
726 		if (result->conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
727 			CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
728 		} else {
729 			CONN_SET_STATE(result->conn, CONN_READY);
730 		}
731 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
732 	}
733 
734 	DBG_RETURN(retrow);
735 }
736 /* }}} */
737 
738 
739 /* {{{ mysqlnd_fetch_row_unbuffered */
740 static enum_func_status
mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)741 mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
742 {
743 	enum_func_status	ret;
744 	zval				*row = (zval *) param;
745 	MYSQLND_PACKET_ROW	*row_packet = result->row_packet;
746 
747 	DBG_ENTER("mysqlnd_fetch_row_unbuffered");
748 
749 	*fetched_anything = FALSE;
750 	if (result->unbuf->eof_reached) {
751 		/* No more rows obviously */
752 		DBG_RETURN(PASS);
753 	}
754 	if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
755 		SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
756 		DBG_RETURN(FAIL);
757 	}
758 	if (!row_packet) {
759 		/* Not fully initialized object that is being cleaned up */
760 		DBG_RETURN(FAIL);
761 	}
762 	/* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
763 	row_packet->skip_extraction = row? FALSE:TRUE;
764 
765 	/*
766 	  If we skip rows (row == NULL) we have to
767 	  result->m.unbuffered_free_last_data() before it. The function returns always true.
768 	*/
769 	if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
770 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
771 
772 		result->unbuf->last_row_data = row_packet->fields;
773 		result->unbuf->last_row_buffer = row_packet->row_buffer;
774 		row_packet->fields = NULL;
775 		row_packet->row_buffer = NULL;
776 
777 		MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
778 
779 		if (!row_packet->skip_extraction) {
780 			HashTable *row_ht = Z_ARRVAL_P(row);
781 			MYSQLND_FIELD *field = result->meta->fields;
782 			struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
783 			unsigned int i, field_count = result->field_count;
784 			unsigned long *lengths = result->lengths;
785 
786 			enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer,
787 											result->unbuf->last_row_data,
788 											field_count,
789 											row_packet->fields_metadata,
790 											result->conn->options->numeric_and_datetime_as_unicode,
791 											result->conn->options->int_and_float_native,
792 											result->conn->stats TSRMLS_CC);
793 			if (PASS != rc) {
794 				DBG_RETURN(FAIL);
795 			}
796 			for (i = 0; i < field_count; i++, field++, hash_key++) {
797 				zval *data = result->unbuf->last_row_data[i];
798 				unsigned int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data);
799 
800 				if (lengths) {
801 					lengths[i] = len;
802 				}
803 
804 				if (flags & MYSQLND_FETCH_NUM) {
805 					Z_ADDREF_P(data);
806 					zend_hash_next_index_insert(row_ht, &data, sizeof(zval *), NULL);
807 				}
808 				if (flags & MYSQLND_FETCH_ASSOC) {
809 					/* zend_hash_quick_update needs length + trailing zero */
810 					/* QQ: Error handling ? */
811 					/*
812 					  zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
813 					  the index is a numeric and convert it to it. This however means constant
814 					  hashing of the column name, which is not needed as it can be precomputed.
815 					*/
816 					Z_ADDREF_P(data);
817 					if (hash_key->is_numeric == FALSE) {
818 #if MYSQLND_UNICODE
819 						zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE,
820 												 hash_key->ustr,
821 												 hash_key->ulen + 1,
822 												 hash_key->key,
823 												 (void *) &data, sizeof(zval *), NULL);
824 #else
825 						zend_hash_quick_update(Z_ARRVAL_P(row),
826 											   field->name,
827 											   field->name_length + 1,
828 											   hash_key->key,
829 											   (void *) &data, sizeof(zval *), NULL);
830 #endif
831 					} else {
832 						zend_hash_index_update(Z_ARRVAL_P(row),
833 											   hash_key->key,
834 											   (void *) &data, sizeof(zval *), NULL);
835 					}
836 				}
837 				if (field->max_length < len) {
838 					field->max_length = len;
839 				}
840 			}
841 		}
842 		*fetched_anything = TRUE;
843 		result->unbuf->row_count++;
844 	} else if (ret == FAIL) {
845 		if (row_packet->error_info.error_no) {
846 			COPY_CLIENT_ERROR(*result->conn->error_info, row_packet->error_info);
847 			DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
848 		}
849 		CONN_SET_STATE(result->conn, CONN_READY);
850 		result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
851 	} else if (row_packet->eof) {
852 		/* Mark the connection as usable again */
853 		DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
854 		result->unbuf->eof_reached = TRUE;
855 		memset(result->conn->upsert_status, 0, sizeof(*result->conn->upsert_status));
856 		result->conn->upsert_status->warning_count = row_packet->warning_count;
857 		result->conn->upsert_status->server_status = row_packet->server_status;
858 		/*
859 		  result->row_packet will be cleaned when
860 		  destroying the result object
861 		*/
862 		if (result->conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
863 			CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
864 		} else {
865 			CONN_SET_STATE(result->conn, CONN_READY);
866 		}
867 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
868 	}
869 
870 	DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
871 	DBG_RETURN(PASS);
872 }
873 /* }}} */
874 
875 
876 /* {{{ mysqlnd_res::use_result */
877 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,use_result)878 MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, zend_bool ps TSRMLS_DC)
879 {
880 	DBG_ENTER("mysqlnd_res::use_result");
881 
882 	SET_EMPTY_ERROR(*result->conn->error_info);
883 
884 	if (ps == FALSE) {
885 		result->type			= MYSQLND_RES_NORMAL;
886 		result->m.fetch_row		= result->m.fetch_row_normal_unbuffered;
887 		result->m.fetch_lengths	= mysqlnd_fetch_lengths_unbuffered;
888 		result->m.row_decoder	= php_mysqlnd_rowp_read_text_protocol;
889 		result->lengths			= mnd_ecalloc(result->field_count, sizeof(unsigned long));
890 		if (!result->lengths) {
891 			goto oom;
892 		}
893 	} else {
894 		result->type			= MYSQLND_RES_PS_UNBUF;
895 		result->m.fetch_row		= NULL;
896 		/* result->m.fetch_row() will be set in mysqlnd_ps.c */
897 		result->m.fetch_lengths	= NULL; /* makes no sense */
898 		result->m.row_decoder	= php_mysqlnd_rowp_read_binary_protocol;
899 		result->lengths 		= NULL;
900 	}
901 
902 	result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC);
903 	result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED));
904 	if (!result->result_set_memory_pool || !result->unbuf) {
905 		goto oom;
906 	}
907 
908 	/*
909 	  Will be freed in the mysqlnd_internal_free_result_contents() called
910 	  by the resource destructor. mysqlnd_fetch_row_unbuffered() expects
911 	  this to be not NULL.
912 	*/
913 	/* FALSE = non-persistent */
914 	result->row_packet = result->conn->protocol->m.get_row_packet(result->conn->protocol, FALSE TSRMLS_CC);
915 	if (!result->row_packet) {
916 		goto oom;
917 	}
918 	result->row_packet->result_set_memory_pool = result->result_set_memory_pool;
919 	result->row_packet->field_count = result->field_count;
920 	result->row_packet->binary_protocol = ps;
921 	result->row_packet->fields_metadata = result->meta->fields;
922 	result->row_packet->bit_fields_count = result->meta->bit_fields_count;
923 	result->row_packet->bit_fields_total_len = result->meta->bit_fields_total_len;
924 
925 	DBG_RETURN(result);
926 oom:
927 	SET_OOM_ERROR(*result->conn->error_info);
928 	DBG_RETURN(NULL);
929 }
930 /* }}} */
931 
932 
933 /* {{{ mysqlnd_fetch_row_buffered_c */
934 static MYSQLND_ROW_C
mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC)935 mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC)
936 {
937 	MYSQLND_ROW_C ret = NULL;
938 	MYSQLND_RES_BUFFERED *set = result->stored_data;
939 
940 	DBG_ENTER("mysqlnd_fetch_row_buffered_c");
941 
942 	/* If we haven't read everything */
943 	if (set->data_cursor &&
944 		(set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
945 	{
946 		zval **current_row = set->data_cursor;
947 		MYSQLND_FIELD *field = result->meta->fields;
948 		struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
949 		unsigned int i;
950 
951 		if (NULL == current_row[0]) {
952 			uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count;
953 			enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num],
954 											current_row,
955 											result->meta->field_count,
956 											result->meta->fields,
957 											result->conn->options->numeric_and_datetime_as_unicode,
958 											result->conn->options->int_and_float_native,
959 											result->conn->stats TSRMLS_CC);
960 			if (rc != PASS) {
961 				DBG_RETURN(ret);
962 			}
963 			set->initialized_rows++;
964 			for (i = 0; i < result->field_count; i++) {
965 				/*
966 				  NULL fields are 0 length, 0 is not more than 0
967 				  String of zero size, definitely can't be the next max_length.
968 				  Thus for NULL and zero-length we are quite efficient.
969 				*/
970 				if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
971 					unsigned long len = Z_STRLEN_P(current_row[i]);
972 					if (field->max_length < len) {
973 						field->max_length = len;
974 					}
975 				}
976 			}
977 		}
978 
979 		set->data_cursor += result->meta->field_count;
980 		MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
981 
982 		ret = mnd_malloc(result->field_count * sizeof(char *));
983 		if (ret) {
984 			for (i = 0; i < result->field_count; i++, field++, hash_key++) {
985 				zval *data = current_row[i];
986 
987 				if (Z_TYPE_P(data) != IS_NULL) {
988 					convert_to_string(data);
989 					ret[i] = Z_STRVAL_P(data);
990 				} else {
991 					ret[i] = NULL;
992 				}
993 			}
994 		}
995 		/* there is no conn handle in this function thus we can't set OOM in error_info */
996 	} else {
997 		set->data_cursor = NULL;
998 		DBG_INF("EOF reached");
999 	}
1000 	DBG_RETURN(ret);
1001 }
1002 /* }}} */
1003 
1004 
1005 /* {{{ mysqlnd_fetch_row_buffered */
1006 static enum_func_status
mysqlnd_fetch_row_buffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)1007 mysqlnd_fetch_row_buffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
1008 {
1009 	unsigned int i;
1010 	zval *row = (zval *) param;
1011 	MYSQLND_RES_BUFFERED *set = result->stored_data;
1012 	enum_func_status ret = FAIL;
1013 
1014 	DBG_ENTER("mysqlnd_fetch_row_buffered");
1015 
1016 	/* If we haven't read everything */
1017 	if (set->data_cursor &&
1018 		(set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
1019 	{
1020 		zval **current_row = set->data_cursor;
1021 		MYSQLND_FIELD *field = result->meta->fields;
1022 		struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
1023 
1024 		if (NULL == current_row[0]) {
1025 			uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count;
1026 			enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num],
1027 											current_row,
1028 											result->meta->field_count,
1029 											result->meta->fields,
1030 											result->conn->options->numeric_and_datetime_as_unicode,
1031 											result->conn->options->int_and_float_native,
1032 											result->conn->stats TSRMLS_CC);
1033 			if (rc != PASS) {
1034 				DBG_RETURN(FAIL);
1035 			}
1036 			set->initialized_rows++;
1037 			for (i = 0; i < result->field_count; i++) {
1038 				/*
1039 				  NULL fields are 0 length, 0 is not more than 0
1040 				  String of zero size, definitely can't be the next max_length.
1041 				  Thus for NULL and zero-length we are quite efficient.
1042 				*/
1043 				if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
1044 					unsigned long len = Z_STRLEN_P(current_row[i]);
1045 					if (field->max_length < len) {
1046 						field->max_length = len;
1047 					}
1048 				}
1049 			}
1050 		}
1051 
1052 		for (i = 0; i < result->field_count; i++, field++, hash_key++) {
1053 			zval *data = current_row[i];
1054 
1055 			if (flags & MYSQLND_FETCH_NUM) {
1056 				Z_ADDREF_P(data);
1057 				zend_hash_next_index_insert(Z_ARRVAL_P(row), &data, sizeof(zval *), NULL);
1058 			}
1059 			if (flags & MYSQLND_FETCH_ASSOC) {
1060 				/* zend_hash_quick_update needs length + trailing zero */
1061 				/* QQ: Error handling ? */
1062 				/*
1063 				  zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1064 				  the index is a numeric and convert it to it. This however means constant
1065 				  hashing of the column name, which is not needed as it can be precomputed.
1066 				*/
1067 				Z_ADDREF_P(data);
1068 				if (hash_key->is_numeric == FALSE) {
1069 #if MYSQLND_UNICODE
1070 					zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE,
1071 											 hash_key->ustr,
1072 											 hash_key->ulen + 1,
1073 											 hash_key->key,
1074 											 (void *) &data, sizeof(zval *), NULL);
1075 #else
1076 					zend_hash_quick_update(Z_ARRVAL_P(row),
1077 										   field->name,
1078 										   field->name_length + 1,
1079 										   hash_key->key,
1080 										   (void *) &data, sizeof(zval *), NULL);
1081 #endif
1082 				} else {
1083 					zend_hash_index_update(Z_ARRVAL_P(row),
1084 										   hash_key->key,
1085 										   (void *) &data, sizeof(zval *), NULL);
1086 				}
1087 			}
1088 		}
1089 		set->data_cursor += result->meta->field_count;
1090 		*fetched_anything = TRUE;
1091 		MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
1092 		ret = PASS;
1093 	} else {
1094 		set->data_cursor = NULL;
1095 		*fetched_anything = FALSE;
1096 		ret = PASS;
1097 		DBG_INF("EOF reached");
1098 	}
1099 	DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
1100 	DBG_RETURN(ret);
1101 }
1102 /* }}} */
1103 
1104 
1105 #define STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY 2
1106 
1107 /* {{{ mysqlnd_res::store_result_fetch_data */
1108 enum_func_status
MYSQLND_METHOD(mysqlnd_res,store_result_fetch_data)1109 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result,
1110 													MYSQLND_RES_METADATA *meta,
1111 													zend_bool binary_protocol TSRMLS_DC)
1112 {
1113 	enum_func_status ret;
1114 	MYSQLND_PACKET_ROW *row_packet = NULL;
1115 	unsigned int next_extend = STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY, free_rows = 1;
1116 	MYSQLND_RES_BUFFERED *set;
1117 
1118 	DBG_ENTER("mysqlnd_res::store_result_fetch_data");
1119 
1120 	result->stored_data	= set = mnd_ecalloc(1, sizeof(MYSQLND_RES_BUFFERED));
1121 	if (!set) {
1122 		SET_OOM_ERROR(*conn->error_info);
1123 		ret = FAIL;
1124 		goto end;
1125 	}
1126 	if (free_rows) {
1127 		set->row_buffers = mnd_emalloc((size_t)(free_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)));
1128 		if (!set->row_buffers) {
1129 			SET_OOM_ERROR(*conn->error_info);
1130 			ret = FAIL;
1131 			goto end;
1132 		}
1133 	}
1134 	set->references	= 1;
1135 
1136 	/* non-persistent */
1137 	row_packet = conn->protocol->m.get_row_packet(conn->protocol, FALSE TSRMLS_CC);
1138 	if (!row_packet) {
1139 		SET_OOM_ERROR(*conn->error_info);
1140 		ret = FAIL;
1141 		goto end;
1142 	}
1143 	row_packet->result_set_memory_pool = result->result_set_memory_pool;
1144 	row_packet->field_count = meta->field_count;
1145 	row_packet->binary_protocol = binary_protocol;
1146 	row_packet->fields_metadata = meta->fields;
1147 	row_packet->bit_fields_count	= meta->bit_fields_count;
1148 	row_packet->bit_fields_total_len = meta->bit_fields_total_len;
1149 
1150 	row_packet->skip_extraction = TRUE; /* let php_mysqlnd_rowp_read() not allocate row_packet->fields, we will do it */
1151 
1152 	while (FAIL != (ret = PACKET_READ(row_packet, conn)) && !row_packet->eof) {
1153 		if (!free_rows) {
1154 			uint64_t total_allocated_rows = free_rows = next_extend = next_extend * 11 / 10; /* extend with 10% */
1155 			MYSQLND_MEMORY_POOL_CHUNK ** new_row_buffers;
1156 			total_allocated_rows += set->row_count;
1157 
1158 			/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1159 			if (total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) {
1160 				SET_OOM_ERROR(*conn->error_info);
1161 				ret = FAIL;
1162 				goto end;
1163 			}
1164 			new_row_buffers = mnd_erealloc(set->row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)));
1165 			if (!new_row_buffers) {
1166 				SET_OOM_ERROR(*conn->error_info);
1167 				ret = FAIL;
1168 				goto end;
1169 			}
1170 			set->row_buffers = new_row_buffers;
1171 		}
1172 		free_rows--;
1173 		set->row_buffers[set->row_count] = row_packet->row_buffer;
1174 
1175 		set->row_count++;
1176 
1177 		/* So row_packet's destructor function won't efree() it */
1178 		row_packet->fields = NULL;
1179 		row_packet->row_buffer = NULL;
1180 
1181 		/*
1182 		  No need to FREE_ALLOCA as we can reuse the
1183 		  'lengths' and 'fields' arrays. For lengths its absolutely safe.
1184 		  'fields' is reused because the ownership of the strings has been
1185 		  transferred above.
1186 		*/
1187 	}
1188 	/* Overflow ? */
1189 	if (set->row_count) {
1190 		/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1191 		if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) {
1192 			SET_OOM_ERROR(*conn->error_info);
1193 			ret = FAIL;
1194 			goto end;
1195 		}
1196 		/* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */
1197 		set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval *)));
1198 		if (!set->data) {
1199 			SET_OOM_ERROR(*conn->error_info);
1200 			ret = FAIL;
1201 			goto end;
1202 		}
1203 		memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval *)));
1204 	}
1205 
1206 	MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats,
1207 									   binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
1208 														STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
1209 									   set->row_count);
1210 
1211 	/* Finally clean */
1212 	if (row_packet->eof) {
1213 		memset(conn->upsert_status, 0, sizeof(*conn->upsert_status));
1214 		conn->upsert_status->warning_count = row_packet->warning_count;
1215 		conn->upsert_status->server_status = row_packet->server_status;
1216 	}
1217 	/* save some memory */
1218 	if (free_rows) {
1219 		/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1220 		if (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) {
1221 			SET_OOM_ERROR(*conn->error_info);
1222 			ret = FAIL;
1223 			goto end;
1224 		}
1225 		set->row_buffers = mnd_erealloc(set->row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)));
1226 	}
1227 
1228 	if (conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
1229 		CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
1230 	} else {
1231 		CONN_SET_STATE(conn, CONN_READY);
1232 	}
1233 
1234 	if (ret == FAIL) {
1235 		COPY_CLIENT_ERROR(set->error_info, row_packet->error_info);
1236 	} else {
1237 		/* Position at the first row */
1238 		set->data_cursor = set->data;
1239 
1240 		/* libmysql's documentation says it should be so for SELECT statements */
1241 		conn->upsert_status->affected_rows = set->row_count;
1242 	}
1243 	DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u",
1244 				ret == PASS? "PASS":"FAIL", (uint) set->row_count, conn->upsert_status->warning_count, conn->upsert_status->server_status);
1245 end:
1246 	PACKET_FREE(row_packet);
1247 
1248 	DBG_RETURN(ret);
1249 }
1250 /* }}} */
1251 
1252 
1253 /* {{{ mysqlnd_res::store_result */
1254 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,store_result)1255 MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
1256 										  MYSQLND_CONN_DATA * const conn,
1257 										  zend_bool ps_protocol TSRMLS_DC)
1258 {
1259 	enum_func_status ret;
1260 
1261 	DBG_ENTER("mysqlnd_res::store_result");
1262 
1263 	/* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
1264 	result->conn 			= conn->m->get_reference(conn TSRMLS_CC);
1265 	result->type			= MYSQLND_RES_NORMAL;
1266 	result->m.fetch_row		= result->m.fetch_row_normal_buffered;
1267 	result->m.fetch_lengths	= mysqlnd_fetch_lengths_buffered;
1268 	result->m.row_decoder = ps_protocol? php_mysqlnd_rowp_read_binary_protocol:
1269 										 php_mysqlnd_rowp_read_text_protocol;
1270 
1271 	result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC);
1272 	result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long));
1273 
1274 	if (!result->result_set_memory_pool || !result->lengths) {
1275 		SET_OOM_ERROR(*conn->error_info);
1276 		DBG_RETURN(NULL);
1277 	}
1278 
1279 	CONN_SET_STATE(conn, CONN_FETCHING_DATA);
1280 
1281 	ret = result->m.store_result_fetch_data(conn, result, result->meta, ps_protocol TSRMLS_CC);
1282 	if (FAIL == ret) {
1283 		if (result->stored_data) {
1284 			COPY_CLIENT_ERROR(*conn->error_info, result->stored_data->error_info);
1285 		} else {
1286 			SET_OOM_ERROR(*conn->error_info);
1287 		}
1288 		DBG_RETURN(NULL);
1289 	}
1290 	/* libmysql's documentation says it should be so for SELECT statements */
1291 	conn->upsert_status->affected_rows = result->stored_data->row_count;
1292 
1293 	DBG_RETURN(result);
1294 }
1295 /* }}} */
1296 
1297 
1298 /* {{{ mysqlnd_res::skip_result */
1299 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,skip_result)1300 MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result TSRMLS_DC)
1301 {
1302 	zend_bool fetched_anything;
1303 
1304 	DBG_ENTER("mysqlnd_res::skip_result");
1305 	/*
1306 	  Unbuffered sets
1307 	  A PS could be prepared - there is metadata and thus a stmt->result but the
1308 	  fetch_row function isn't actually set (NULL), thus we have to skip these.
1309 	*/
1310 	if (!result->stored_data && result->unbuf &&
1311 		!result->unbuf->eof_reached && result->m.fetch_row)
1312 	{
1313 		DBG_INF("skipping result");
1314 		/* We have to fetch all data to clean the line */
1315 		MYSQLND_INC_CONN_STATISTIC(result->conn->stats,
1316 									result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS:
1317 																		STAT_FLUSHED_PS_SETS);
1318 
1319 		while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything TSRMLS_CC)) && fetched_anything == TRUE) {
1320 			/* do nothing */;
1321 		}
1322 	}
1323 	DBG_RETURN(PASS);
1324 }
1325 /* }}} */
1326 
1327 
1328 /* {{{ mysqlnd_res::free_result */
1329 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,free_result)1330 MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC)
1331 {
1332 	DBG_ENTER("mysqlnd_res::free_result");
1333 
1334 	result->m.skip_result(result TSRMLS_CC);
1335 	MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL,
1336 							   implicit == TRUE?	STAT_FREE_RESULT_IMPLICIT:
1337 							   						STAT_FREE_RESULT_EXPLICIT);
1338 
1339 	result->m.free_result_internal(result TSRMLS_CC);
1340 	DBG_RETURN(PASS);
1341 }
1342 /* }}} */
1343 
1344 
1345 /* {{{ mysqlnd_res::data_seek */
1346 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,data_seek)1347 MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * result, uint64_t row TSRMLS_DC)
1348 {
1349 	DBG_ENTER("mysqlnd_res::data_seek");
1350 	DBG_INF_FMT("row=%lu", row);
1351 
1352 	if (!result->stored_data) {
1353 		return FAIL;
1354 	}
1355 
1356 	/* libmysql just moves to the end, it does traversing of a linked list */
1357 	if (row >= result->stored_data->row_count) {
1358 		result->stored_data->data_cursor = NULL;
1359 	} else {
1360 		result->stored_data->data_cursor = result->stored_data->data + row * result->meta->field_count;
1361 	}
1362 
1363 	DBG_RETURN(PASS);
1364 }
1365 /* }}} */
1366 
1367 
1368 /* {{{ mysqlnd_res::num_rows */
1369 static uint64_t
MYSQLND_METHOD(mysqlnd_res,num_rows)1370 MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result TSRMLS_DC)
1371 {
1372 	/* Be compatible with libmysql. We count row_count, but will return 0 */
1373 	return result->stored_data? result->stored_data->row_count:(result->unbuf && result->unbuf->eof_reached? result->unbuf->row_count:0);
1374 }
1375 /* }}} */
1376 
1377 
1378 /* {{{ mysqlnd_res::num_fields */
1379 static unsigned int
MYSQLND_METHOD(mysqlnd_res,num_fields)1380 MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result TSRMLS_DC)
1381 {
1382 	return result->field_count;
1383 }
1384 /* }}} */
1385 
1386 
1387 /* {{{ mysqlnd_res::fetch_field */
1388 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field)1389 MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result TSRMLS_DC)
1390 {
1391 	DBG_ENTER("mysqlnd_res::fetch_field");
1392 	do {
1393 		if (result->meta) {
1394 			/*
1395 			  We optimize the result set, so we don't convert all the data from raw buffer format to
1396 			  zval arrays during store. In the case someone doesn't read all the lines this will
1397 			  save time. However, when a metadata call is done, we need to calculate max_length.
1398 			  We don't have control whether max_length will be used, unfortunately. Otherwise we
1399 			  could have been able to skip that step.
1400 			  Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1401 			  then we can have max_length as dynamic property, which will be calculated during runtime and
1402 			  not during mysqli_fetch_field() time.
1403 			*/
1404 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1405 				DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1406 				/* we have to initialize the rest to get the updated max length */
1407 				if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
1408 					break;
1409 				}
1410 			}
1411 			DBG_RETURN(result->meta->m->fetch_field(result->meta TSRMLS_CC));
1412 		}
1413 	} while (0);
1414 	DBG_RETURN(NULL);
1415 }
1416 /* }}} */
1417 
1418 
1419 /* {{{ mysqlnd_res::fetch_field_direct */
1420 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field_direct)1421 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC)
1422 {
1423 	DBG_ENTER("mysqlnd_res::fetch_field_direct");
1424 	do {
1425 		if (result->meta) {
1426 			/*
1427 			  We optimize the result set, so we don't convert all the data from raw buffer format to
1428 			  zval arrays during store. In the case someone doesn't read all the lines this will
1429 			  save time. However, when a metadata call is done, we need to calculate max_length.
1430 			  We don't have control whether max_length will be used, unfortunately. Otherwise we
1431 			  could have been able to skip that step.
1432 			  Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1433 			  then we can have max_length as dynamic property, which will be calculated during runtime and
1434 			  not during mysqli_fetch_field_direct() time.
1435 			*/
1436 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1437 				DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1438 				/* we have to initialized the rest to get the updated max length */
1439 				if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
1440 					break;
1441 				}
1442 			}
1443 			DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr TSRMLS_CC));
1444 		}
1445 	} while (0);
1446 
1447 	DBG_RETURN(NULL);
1448 }
1449 /* }}} */
1450 
1451 
1452 /* {{{ mysqlnd_res::fetch_field */
1453 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_fields)1454 MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result TSRMLS_DC)
1455 {
1456 	DBG_ENTER("mysqlnd_res::fetch_fields");
1457 	do {
1458 		if (result->meta) {
1459 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1460 				/* we have to initialize the rest to get the updated max length */
1461 				if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
1462 					break;
1463 				}
1464 			}
1465 			DBG_RETURN(result->meta->m->fetch_fields(result->meta TSRMLS_CC));
1466 		}
1467 	} while (0);
1468 	DBG_RETURN(NULL);
1469 }
1470 /* }}} */
1471 
1472 
1473 
1474 /* {{{ mysqlnd_res::field_seek */
1475 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_seek)1476 MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC)
1477 {
1478 	MYSQLND_FIELD_OFFSET return_value = 0;
1479 	if (result->meta) {
1480 		return_value = result->meta->current_field;
1481 		result->meta->current_field = field_offset;
1482 	}
1483 	return return_value;
1484 }
1485 /* }}} */
1486 
1487 
1488 /* {{{ mysqlnd_res::field_tell */
1489 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_tell)1490 MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result TSRMLS_DC)
1491 {
1492 	return result->meta? result->meta->m->field_tell(result->meta TSRMLS_CC) : 0;
1493 }
1494 /* }}} */
1495 
1496 
1497 /* {{{ mysqlnd_res::fetch_into */
1498 static void
MYSQLND_METHOD(mysqlnd_res,fetch_into)1499 MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, unsigned int flags,
1500 										zval *return_value,
1501 										enum_mysqlnd_extension extension TSRMLS_DC ZEND_FILE_LINE_DC)
1502 {
1503 	zend_bool fetched_anything;
1504 
1505 	DBG_ENTER("mysqlnd_res::fetch_into");
1506 
1507 	if (!result->m.fetch_row) {
1508 		RETVAL_NULL();
1509 		DBG_VOID_RETURN;
1510 	}
1511 	/*
1512 	  Hint Zend how many elements we will have in the hash. Thus it won't
1513 	  extend and rehash the hash constantly.
1514 	*/
1515 	mysqlnd_array_init(return_value, mysqlnd_num_fields(result) * 2);
1516 	if (FAIL == result->m.fetch_row(result, (void *)return_value, flags, &fetched_anything TSRMLS_CC)) {
1517 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading a row");
1518 		zval_dtor(return_value);
1519 		RETVAL_FALSE;
1520 	} else if (fetched_anything == FALSE) {
1521 		zval_dtor(return_value);
1522 		switch (extension) {
1523 			case MYSQLND_MYSQLI:
1524 				RETVAL_NULL();
1525 				break;
1526 			case MYSQLND_MYSQL:
1527 				RETVAL_FALSE;
1528 				break;
1529 			default:exit(0);
1530 		}
1531 	}
1532 	/*
1533 	  return_value is IS_NULL for no more data and an array for data. Thus it's ok
1534 	  to return here.
1535 	*/
1536 	DBG_VOID_RETURN;
1537 }
1538 /* }}} */
1539 
1540 
1541 /* {{{ mysqlnd_res::fetch_row_c */
1542 static MYSQLND_ROW_C
MYSQLND_METHOD(mysqlnd_res,fetch_row_c)1543 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result TSRMLS_DC)
1544 {
1545 	MYSQLND_ROW_C ret = NULL;
1546 	DBG_ENTER("mysqlnd_res::fetch_row_c");
1547 
1548 	if (result->m.fetch_row) {
1549 		if (result->m.fetch_row == result->m.fetch_row_normal_buffered) {
1550 			DBG_RETURN(mysqlnd_fetch_row_buffered_c(result TSRMLS_CC));
1551 		} else if (result->m.fetch_row == result->m.fetch_row_normal_unbuffered) {
1552 			DBG_RETURN(mysqlnd_fetch_row_unbuffered_c(result TSRMLS_CC));
1553 		} else {
1554 			php_error_docref(NULL TSRMLS_CC, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers");
1555 		}
1556 	}
1557 	DBG_RETURN(ret);
1558 }
1559 /* }}} */
1560 
1561 
1562 /* {{{ mysqlnd_res::fetch_all */
1563 static void
MYSQLND_METHOD(mysqlnd_res,fetch_all)1564 MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC)
1565 {
1566 	zval  *row;
1567 	ulong i = 0;
1568 	MYSQLND_RES_BUFFERED *set = result->stored_data;
1569 
1570 	DBG_ENTER("mysqlnd_res::fetch_all");
1571 
1572 	if ((!result->unbuf && !set)) {
1573 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "fetch_all can be used only with buffered sets");
1574 		if (result->conn) {
1575 			SET_CLIENT_ERROR(*result->conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "fetch_all can be used only with buffered sets");
1576 		}
1577 		RETVAL_NULL();
1578 		DBG_VOID_RETURN;
1579 	}
1580 
1581 	/* 4 is a magic value. The cast is safe, if larger then the array will be later extended - no big deal :) */
1582 	mysqlnd_array_init(return_value, set? (unsigned int) set->row_count : 4);
1583 
1584 	do {
1585 		MAKE_STD_ZVAL(row);
1586 		mysqlnd_fetch_into(result, flags, row, MYSQLND_MYSQLI);
1587 		if (Z_TYPE_P(row) != IS_ARRAY) {
1588 			zval_ptr_dtor(&row);
1589 			break;
1590 		}
1591 		add_index_zval(return_value, i++, row);
1592 	} while (1);
1593 
1594 	DBG_VOID_RETURN;
1595 }
1596 /* }}} */
1597 
1598 
1599 /* {{{ mysqlnd_res::fetch_field_data */
1600 static void
MYSQLND_METHOD(mysqlnd_res,fetch_field_data)1601 MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, unsigned int offset, zval *return_value TSRMLS_DC)
1602 {
1603 	zval row;
1604 	zval **entry;
1605 	unsigned int i = 0;
1606 
1607 	DBG_ENTER("mysqlnd_res::fetch_field_data");
1608 	DBG_INF_FMT("offset=%u", offset);
1609 
1610 	if (!result->m.fetch_row) {
1611 		RETVAL_NULL();
1612 		DBG_VOID_RETURN;
1613 	}
1614 	/*
1615 	  Hint Zend how many elements we will have in the hash. Thus it won't
1616 	  extend and rehash the hash constantly.
1617 	*/
1618 	INIT_PZVAL(&row);
1619 	mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, &row, MYSQLND_MYSQL);
1620 	if (Z_TYPE(row) != IS_ARRAY) {
1621 		zval_dtor(&row);
1622 		RETVAL_NULL();
1623 		DBG_VOID_RETURN;
1624 	}
1625 	zend_hash_internal_pointer_reset(Z_ARRVAL(row));
1626 	while (i++ < offset) {
1627 		zend_hash_move_forward(Z_ARRVAL(row));
1628 		zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry);
1629 	}
1630 
1631 	zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry);
1632 
1633 	*return_value = **entry;
1634 	zval_copy_ctor(return_value);
1635 	Z_SET_REFCOUNT_P(return_value, 1);
1636 	zval_dtor(&row);
1637 
1638 	DBG_VOID_RETURN;
1639 }
1640 /* }}} */
1641 
1642 
1643 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1644 	NULL, /* fetch_row */
1645 	mysqlnd_fetch_row_buffered,
1646 	mysqlnd_fetch_row_unbuffered,
1647 	MYSQLND_METHOD(mysqlnd_res, use_result),
1648 	MYSQLND_METHOD(mysqlnd_res, store_result),
1649 	MYSQLND_METHOD(mysqlnd_res, fetch_into),
1650 	MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1651 	MYSQLND_METHOD(mysqlnd_res, fetch_all),
1652 	MYSQLND_METHOD(mysqlnd_res, fetch_field_data),
1653 	MYSQLND_METHOD(mysqlnd_res, num_rows),
1654 	MYSQLND_METHOD(mysqlnd_res, num_fields),
1655 	MYSQLND_METHOD(mysqlnd_res, skip_result),
1656 	MYSQLND_METHOD(mysqlnd_res, data_seek),
1657 	MYSQLND_METHOD(mysqlnd_res, field_seek),
1658 	MYSQLND_METHOD(mysqlnd_res, field_tell),
1659 	MYSQLND_METHOD(mysqlnd_res, fetch_field),
1660 	MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1661 	MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1662 	MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1663 	NULL, /* fetch_lengths */
1664 	MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1665 	MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest),
1666 	MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1667 	MYSQLND_METHOD(mysqlnd_res, free_result),
1668 
1669 	mysqlnd_internal_free_result, /* free_result_internal */
1670 	mysqlnd_internal_free_result_contents, /* free_result_contents */
1671 	MYSQLND_METHOD(mysqlnd_res, free_buffered_data),
1672 	MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data),
1673 
1674 	NULL /* row_decoder */,
1675 	mysqlnd_result_meta_init
1676 MYSQLND_CLASS_METHODS_END;
1677 
1678 
1679 /* {{{ mysqlnd_result_init */
1680 PHPAPI MYSQLND_RES *
mysqlnd_result_init(unsigned int field_count,zend_bool persistent TSRMLS_DC)1681 mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC)
1682 {
1683 	size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1684 	MYSQLND_RES *ret = mnd_pecalloc(1, alloc_size, persistent);
1685 
1686 	DBG_ENTER("mysqlnd_result_init");
1687 
1688 	if (!ret) {
1689 		DBG_RETURN(NULL);
1690 	}
1691 
1692 	ret->persistent		= persistent;
1693 	ret->field_count	= field_count;
1694 	ret->m = *mysqlnd_result_get_methods();
1695 
1696 	DBG_RETURN(ret);
1697 }
1698 /* }}} */
1699 
1700 
1701 /*
1702  * Local variables:
1703  * tab-width: 4
1704  * c-basic-offset: 4
1705  * End:
1706  * vim600: noet sw=4 ts=4 fdm=marker
1707  * vim<600: noet sw=4 ts=4
1708  */
1709