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