xref: /PHP-7.4/ext/mysqlnd/mysqlnd_result.c (revision e450621f)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 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@php.net>                             |
16   |          Ulf Wendel <uw@php.net>                                     |
17   +----------------------------------------------------------------------+
18 */
19 
20 #include "php.h"
21 #include "mysqlnd.h"
22 #include "mysqlnd_wireprotocol.h"
23 #include "mysqlnd_block_alloc.h"
24 #include "mysqlnd_connection.h"
25 #include "mysqlnd_priv.h"
26 #include "mysqlnd_result.h"
27 #include "mysqlnd_result_meta.h"
28 #include "mysqlnd_statistics.h"
29 #include "mysqlnd_debug.h"
30 #include "mysqlnd_ext_plugin.h"
31 
32 /* {{{ mysqlnd_result_buffered_zval::initialize_result_set_rest */
33 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_zval,initialize_result_set_rest)34 MYSQLND_METHOD(mysqlnd_result_buffered_zval, initialize_result_set_rest)(MYSQLND_RES_BUFFERED * const result,
35 																		 MYSQLND_RES_METADATA * const meta,
36 																		 MYSQLND_STATS * stats,
37 																		 zend_bool int_and_float_native)
38 {
39 	enum_func_status ret = PASS;
40 	const unsigned int field_count = meta->field_count;
41 	const uint64_t row_count = result->row_count;
42 
43 	zval *data_begin = ((MYSQLND_RES_BUFFERED_ZVAL *) result)->data;
44 	zval *data_cursor = data_begin;
45 
46 	DBG_ENTER("mysqlnd_result_buffered_zval::initialize_result_set_rest");
47 
48 	if (!data_cursor || row_count == result->initialized_rows) {
49 		DBG_RETURN(ret);
50 	}
51 	while ((data_cursor - data_begin) < (int)(row_count * field_count)) {
52 		if (Z_ISUNDEF(data_cursor[0])) {
53 			unsigned int i;
54 			const size_t current_row_num = (data_cursor - data_begin) / field_count;
55 			enum_func_status rc = result->m.row_decoder(&result->row_buffers[current_row_num],
56 														data_cursor,
57 														field_count,
58 														meta->fields,
59 														int_and_float_native,
60 														stats);
61 			if (rc != PASS) {
62 				ret = FAIL;
63 				break;
64 			}
65 			++result->initialized_rows;
66 			for (i = 0; i < 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(data_cursor[i]) == IS_STRING) {
73 					const size_t len = Z_STRLEN(data_cursor[i]);
74 					if (meta->fields[i].max_length < len) {
75 						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_result_buffered_c::initialize_result_set_rest */
88 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_c,initialize_result_set_rest)89 MYSQLND_METHOD(mysqlnd_result_buffered_c, initialize_result_set_rest)(MYSQLND_RES_BUFFERED * const result,
90 																	  MYSQLND_RES_METADATA * const meta,
91 																	  MYSQLND_STATS * stats,
92 																	  const zend_bool int_and_float_native)
93 {
94 	unsigned int row, field;
95 	enum_func_status ret = PASS;
96 	const unsigned int field_count = meta->field_count;
97 	const uint64_t row_count = result->row_count;
98 	enum_func_status rc;
99 	DBG_ENTER("mysqlnd_result_buffered_c::initialize_result_set_rest");
100 
101 	if (result->initialized_rows < row_count) {
102 		zend_uchar * initialized = ((MYSQLND_RES_BUFFERED_C *) result)->initialized;
103 		zval * current_row = mnd_emalloc(field_count * sizeof(zval));
104 
105 		if (!current_row) {
106 			DBG_RETURN(FAIL);
107 		}
108 
109 		for (row = 0; row < result->row_count; row++) {
110 			/* (row / 8) & the_bit_for_row*/
111 			if (ZEND_BIT_TEST(initialized, row)) {
112 				continue;
113 			}
114 
115 			rc = result->m.row_decoder(&result->row_buffers[row], current_row, field_count, meta->fields, int_and_float_native, stats);
116 
117 			if (rc != PASS) {
118 				ret = FAIL;
119 				break;
120 			}
121 			result->initialized_rows++;
122 			initialized[row >> 3] |= (1 << (row & 7));
123 			for (field = 0; field < field_count; field++) {
124 				/*
125 				  NULL fields are 0 length, 0 is not more than 0
126 				  String of zero size, definitely can't be the next max_length.
127 				  Thus for NULL and zero-length we are quite efficient.
128 				*/
129 				if (Z_TYPE(current_row[field]) == IS_STRING) {
130 					const size_t len = Z_STRLEN(current_row[field]);
131 					if (meta->fields[field].max_length < len) {
132 						meta->fields[field].max_length = len;
133 					}
134 				}
135 				zval_ptr_dtor_nogc(&current_row[field]);
136 			}
137 		}
138 		mnd_efree(current_row);
139 	}
140 	DBG_RETURN(ret);
141 }
142 /* }}} */
143 
144 
145 /* {{{ mysqlnd_result_unbuffered::free_last_data */
146 static void
MYSQLND_METHOD(mysqlnd_result_unbuffered,free_last_data)147 MYSQLND_METHOD(mysqlnd_result_unbuffered, free_last_data)(MYSQLND_RES_UNBUFFERED * unbuf, MYSQLND_STATS * const global_stats)
148 {
149 	DBG_ENTER("mysqlnd_res::unbuffered_free_last_data");
150 
151 	if (!unbuf) {
152 		DBG_VOID_RETURN;
153 	}
154 
155 	DBG_INF_FMT("field_count=%u", unbuf->field_count);
156 	if (unbuf->last_row_data) {
157 		unsigned int i;
158 		for (i = 0; i < unbuf->field_count; i++) {
159 			zval_ptr_dtor_nogc(&(unbuf->last_row_data[i]));
160 		}
161 
162 		/* Free last row's zvals */
163 		mnd_efree(unbuf->last_row_data);
164 		unbuf->last_row_data = NULL;
165 	}
166 	if (unbuf->last_row_buffer.ptr) {
167 		DBG_INF("Freeing last row buffer");
168 		/* Nothing points to this buffer now, free it */
169 		unbuf->result_set_memory_pool->free_chunk(
170 			unbuf->result_set_memory_pool, unbuf->last_row_buffer.ptr);
171 		unbuf->last_row_buffer.ptr = NULL;
172 	}
173 
174 	DBG_VOID_RETURN;
175 }
176 /* }}} */
177 
178 
179 /* {{{ mysqlnd_result_unbuffered::free_result */
180 static void
MYSQLND_METHOD(mysqlnd_result_unbuffered,free_result)181 MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)(MYSQLND_RES_UNBUFFERED * const result, MYSQLND_STATS * const global_stats)
182 {
183 	DBG_ENTER("mysqlnd_result_unbuffered, free_result");
184 	result->m.free_last_data(result, global_stats);
185 
186 	/* must be free before because references the memory pool */
187 	if (result->row_packet) {
188 		PACKET_FREE(result->row_packet);
189 		mnd_efree(result->row_packet);
190 		result->row_packet = NULL;
191 	}
192 
193 	DBG_VOID_RETURN;
194 }
195 /* }}} */
196 
197 
198 /* {{{ mysqlnd_result_buffered_zval::free_result */
199 static void
MYSQLND_METHOD(mysqlnd_result_buffered_zval,free_result)200 MYSQLND_METHOD(mysqlnd_result_buffered_zval, free_result)(MYSQLND_RES_BUFFERED_ZVAL * const set)
201 {
202 	zval * data = set->data;
203 
204 	DBG_ENTER("mysqlnd_result_buffered_zval::free_result");
205 
206 	set->data = NULL; /* prevent double free if following loop is interrupted */
207 	if (data) {
208 		const unsigned int field_count = set->field_count;
209 		int64_t row;
210 
211 		for (row = set->row_count - 1; row >= 0; row--) {
212 			zval *current_row = data + row * field_count;
213 			int64_t col;
214 
215 			if (current_row != NULL) {
216 				for (col = field_count - 1; col >= 0; --col) {
217 					zval_ptr_dtor_nogc(&(current_row[col]));
218 				}
219 			}
220 		}
221 		mnd_efree(data);
222 	}
223 	set->data_cursor = NULL;
224 	DBG_VOID_RETURN;
225 }
226 /* }}} */
227 
228 
229 /* {{{ mysqlnd_result_buffered_c::free_result */
230 static void
MYSQLND_METHOD(mysqlnd_result_buffered_c,free_result)231 MYSQLND_METHOD(mysqlnd_result_buffered_c, free_result)(MYSQLND_RES_BUFFERED_C * const set)
232 {
233 	DBG_ENTER("mysqlnd_result_buffered_c::free_result");
234 	mnd_efree(set->initialized);
235 	set->initialized = NULL;
236 	DBG_VOID_RETURN;
237 }
238 /* }}} */
239 
240 
241 /* {{{ mysqlnd_result_buffered::free_result */
242 static void
MYSQLND_METHOD(mysqlnd_result_buffered,free_result)243 MYSQLND_METHOD(mysqlnd_result_buffered, free_result)(MYSQLND_RES_BUFFERED * const set)
244 {
245 
246 	DBG_ENTER("mysqlnd_result_buffered::free_result");
247 	DBG_INF_FMT("Freeing "PRIu64" row(s)", set->row_count);
248 
249 	mysqlnd_error_info_free_contents(&set->error_info);
250 
251 	if (set->type == MYSQLND_BUFFERED_TYPE_ZVAL) {
252 		MYSQLND_METHOD(mysqlnd_result_buffered_zval, free_result)((MYSQLND_RES_BUFFERED_ZVAL *) set);
253 	} if (set->type == MYSQLND_BUFFERED_TYPE_C) {
254 		MYSQLND_METHOD(mysqlnd_result_buffered_c, free_result)((MYSQLND_RES_BUFFERED_C *) set);
255 	}
256 
257 	if (set->row_buffers) {
258 		mnd_efree(set->row_buffers);
259 		set->row_buffers = NULL;
260 	}
261 
262 	DBG_VOID_RETURN;
263 }
264 /* }}} */
265 
266 
267 /* {{{ mysqlnd_res::free_result_buffers */
268 static void
MYSQLND_METHOD(mysqlnd_res,free_result_buffers)269 MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES * result)
270 {
271 	DBG_ENTER("mysqlnd_res::free_result_buffers");
272 	DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown"));
273 
274 	if (result->meta) {
275 		ZEND_ASSERT(zend_arena_contains(result->memory_pool->arena, result->meta));
276 		result->meta->m->free_metadata(result->meta);
277 		result->meta = NULL;
278 	}
279 
280 	if (result->unbuf) {
281 		result->unbuf->m.free_result(result->unbuf, result->conn? result->conn->stats : NULL);
282 		result->unbuf = NULL;
283 	} else if (result->stored_data) {
284 		result->stored_data->m.free_result(result->stored_data);
285 		result->stored_data = NULL;
286 	}
287 
288 	mysqlnd_mempool_restore_state(result->memory_pool);
289 	mysqlnd_mempool_save_state(result->memory_pool);
290 
291 	DBG_VOID_RETURN;
292 }
293 /* }}} */
294 
295 
296 /* {{{ mysqlnd_res::free_result_contents_internal */
297 static
MYSQLND_METHOD(mysqlnd_res,free_result_contents_internal)298 void MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal)(MYSQLND_RES * result)
299 {
300 	DBG_ENTER("mysqlnd_res::free_result_contents_internal");
301 
302 	result->m.free_result_buffers(result);
303 
304 	if (result->conn) {
305 		result->conn->m->free_reference(result->conn);
306 		result->conn = NULL;
307 	}
308 
309 	mysqlnd_mempool_destroy(result->memory_pool);
310 
311 	DBG_VOID_RETURN;
312 }
313 /* }}} */
314 
315 
316 /* {{{ mysqlnd_res::free_result_internal */
317 static
MYSQLND_METHOD(mysqlnd_res,free_result_internal)318 void MYSQLND_METHOD(mysqlnd_res, free_result_internal)(MYSQLND_RES * result)
319 {
320 	DBG_ENTER("mysqlnd_res::free_result_internal");
321 
322 	result->m.skip_result(result);
323 	result->m.free_result_contents(result);
324 
325 	DBG_VOID_RETURN;
326 }
327 /* }}} */
328 
329 
330 /* {{{ mysqlnd_res::read_result_metadata */
331 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,read_result_metadata)332 MYSQLND_METHOD(mysqlnd_res, read_result_metadata)(MYSQLND_RES * result, MYSQLND_CONN_DATA * conn)
333 {
334 	DBG_ENTER("mysqlnd_res::read_result_metadata");
335 
336 	/*
337 	  Make it safe to call it repeatedly for PS -
338 	  better free and allocate a new because the number of field might change
339 	  (select *) with altered table. Also for statements which skip the PS
340 	  infrastructure!
341 	*/
342 	if (result->meta) {
343 		result->meta->m->free_metadata(result->meta);
344 		result->meta = NULL;
345 	}
346 
347 	result->meta = result->m.result_meta_init(result, result->field_count);
348 	if (!result->meta) {
349 		SET_OOM_ERROR(conn->error_info);
350 		DBG_RETURN(FAIL);
351 	}
352 
353 	/* 1. Read all fields metadata */
354 
355 	/* It's safe to reread without freeing */
356 	if (FAIL == result->meta->m->read_metadata(result->meta, conn, result)) {
357 		result->meta->m->free_metadata(result->meta);
358 		result->meta = NULL;
359 		DBG_RETURN(FAIL);
360 	}
361 	/* COM_FIELD_LIST is broken and has premature EOF, thus we need to hack here and in mysqlnd_res_meta.c */
362 	result->field_count = result->meta->field_count;
363 
364 	/*
365 	  2. Follows an EOF packet, which the client of mysqlnd_read_result_metadata()
366 	     should consume.
367 	  3. If there is a result set, it follows. The last packet will have 'eof' set
368 	     If PS, then no result set follows.
369 	*/
370 
371 	DBG_RETURN(PASS);
372 }
373 /* }}} */
374 
375 
376 /* {{{ mysqlnd_query_read_result_set_header */
377 enum_func_status
mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn,MYSQLND_STMT * s)378 mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s)
379 {
380 	enum_func_status ret;
381 	MYSQLND_STMT_DATA * stmt = s ? s->data : NULL;
382 	MYSQLND_PACKET_RSET_HEADER rset_header;
383 	MYSQLND_PACKET_EOF fields_eof;
384 
385 	DBG_ENTER("mysqlnd_query_read_result_set_header");
386 	DBG_INF_FMT("stmt=%lu", stmt? stmt->stmt_id:0);
387 
388 	ret = FAIL;
389 	do {
390 		conn->payload_decoder_factory->m.init_rset_header_packet(&rset_header);
391 		UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
392 
393 		if (FAIL == (ret = PACKET_READ(conn, &rset_header))) {
394 			if (conn->error_info->error_no != CR_SERVER_GONE_ERROR) {
395 				php_error_docref(NULL, E_WARNING, "Error reading result set's header");
396 			}
397 			break;
398 		}
399 
400 		if (rset_header.error_info.error_no) {
401 			/*
402 			  Cover a protocol design error: error packet does not
403 			  contain the server status. Therefore, the client has no way
404 			  to find out whether there are more result sets of
405 			  a multiple-result-set statement pending. Luckily, in 5.0 an
406 			  error always aborts execution of a statement, wherever it is
407 			  a multi-statement or a stored procedure, so it should be
408 			  safe to unconditionally turn off the flag here.
409 			*/
410 			UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & ~SERVER_MORE_RESULTS_EXISTS);
411 			/*
412 			  This will copy the error code and the messages, as they
413 			  are buffers in the struct
414 			*/
415 			COPY_CLIENT_ERROR(conn->error_info, rset_header.error_info);
416 			ret = FAIL;
417 			DBG_ERR_FMT("error=%s", rset_header.error_info.error);
418 			/* Return back from CONN_QUERY_SENT */
419 			SET_CONNECTION_STATE(&conn->state, CONN_READY);
420 			break;
421 		}
422 		conn->error_info->error_no = 0;
423 
424 		switch (rset_header.field_count) {
425 			case MYSQLND_NULL_LENGTH: {	/* LOAD DATA LOCAL INFILE */
426 				zend_bool is_warning;
427 				DBG_INF("LOAD DATA");
428 				conn->last_query_type = QUERY_LOAD_LOCAL;
429 				conn->field_count = 0; /* overwrite previous value, or the last value could be used and lead to bug#53503 */
430 				SET_CONNECTION_STATE(&conn->state, CONN_SENDING_LOAD_DATA);
431 				ret = mysqlnd_handle_local_infile(conn, rset_header.info_or_local_file.s, &is_warning);
432 				SET_CONNECTION_STATE(&conn->state,  (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT);
433 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
434 				break;
435 			}
436 			case 0:				/* UPSERT */
437 				DBG_INF("UPSERT");
438 				conn->last_query_type = QUERY_UPSERT;
439 				conn->field_count = rset_header.field_count;
440 				UPSERT_STATUS_RESET(conn->upsert_status);
441 				UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, rset_header.warning_count);
442 				UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, rset_header.server_status);
443 				UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, rset_header.affected_rows);
444 				UPSERT_STATUS_SET_LAST_INSERT_ID(conn->upsert_status, rset_header.last_insert_id);
445 				SET_NEW_MESSAGE(conn->last_message.s, conn->last_message.l,
446 								rset_header.info_or_local_file.s, rset_header.info_or_local_file.l);
447 				/* Result set can follow UPSERT statement, check server_status */
448 				if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
449 					SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
450 				} else {
451 					SET_CONNECTION_STATE(&conn->state, CONN_READY);
452 				}
453 				ret = PASS;
454 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
455 				break;
456 			default: do {			/* Result set */
457 				MYSQLND_RES * result;
458 				enum_mysqlnd_collected_stats statistic = STAT_LAST;
459 
460 				DBG_INF("Result set pending");
461 				SET_EMPTY_MESSAGE(conn->last_message.s, conn->last_message.l);
462 
463 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_RSET_QUERY);
464 				UPSERT_STATUS_RESET(conn->upsert_status);
465 				/* restore after zeroing */
466 				UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
467 
468 				conn->last_query_type = QUERY_SELECT;
469 				SET_CONNECTION_STATE(&conn->state, CONN_FETCHING_DATA);
470 				/* PS has already allocated it */
471 				conn->field_count = rset_header.field_count;
472 				if (!stmt) {
473 					result = conn->current_result = conn->m->result_init(rset_header.field_count);
474 				} else {
475 					if (!stmt->result) {
476 						DBG_INF("This is 'SHOW'/'EXPLAIN'-like query.");
477 						/*
478 						  This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
479 						  prepared statements can't send result set metadata for these queries
480 						  on prepare stage. Read it now.
481 						*/
482 						result = stmt->result = conn->m->result_init(rset_header.field_count);
483 					} else {
484 						/*
485 						  Update result set metadata if it for some reason changed between
486 						  prepare and execute, i.e.:
487 						  - in case of 'SELECT ?' we don't know column type unless data was
488 							supplied to mysql_stmt_execute, so updated column type is sent
489 							now.
490 						  - if data dictionary changed between prepare and execute, for
491 							example a table used in the query was altered.
492 						  Note, that now (4.1.3) we always send metadata in reply to
493 						  COM_STMT_EXECUTE (even if it is not necessary), so either this or
494 						  previous branch always works.
495 						*/
496 					}
497 					result = stmt->result;
498 				}
499 				if (!result) {
500 					SET_OOM_ERROR(conn->error_info);
501 					ret = FAIL;
502 					break;
503 				}
504 
505 				if (FAIL == (ret = result->m.read_result_metadata(result, conn))) {
506 					/* For PS, we leave them in Prepared state */
507 					if (!stmt && conn->current_result) {
508 						mnd_efree(conn->current_result);
509 						conn->current_result = NULL;
510 					}
511 					DBG_ERR("Error occurred while reading metadata");
512 					break;
513 				}
514 
515 				/* Check for SERVER_STATUS_MORE_RESULTS if needed */
516 				conn->payload_decoder_factory->m.init_eof_packet(&fields_eof);
517 				if (FAIL == (ret = PACKET_READ(conn, &fields_eof))) {
518 					DBG_ERR("Error occurred while reading the EOF packet");
519 					result->m.free_result_contents(result);
520 					if (!stmt) {
521 						conn->current_result = NULL;
522 					} else {
523 						stmt->result = NULL;
524 						/* XXX: This will crash, because we will null also the methods.
525 							But seems it happens in extreme cases or doesn't. Should be fixed by exporting a function
526 							(from mysqlnd_driver.c?) to do the reset.
527 							This is done also in mysqlnd_ps.c
528 						*/
529 						memset(stmt, 0, sizeof(*stmt));
530 						stmt->state = MYSQLND_STMT_INITTED;
531 					}
532 				} else {
533 					DBG_INF_FMT("warnings=%u server_status=%u", fields_eof.warning_count, fields_eof.server_status);
534 					UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, fields_eof.warning_count);
535 					/*
536 					  If SERVER_MORE_RESULTS_EXISTS is set then this is either MULTI_QUERY or a CALL()
537 					  The first packet after sending the query/com_execute has the bit set only
538 					  in this cases. Not sure why it's a needed but it marks that the whole stream
539 					  will include many result sets. What actually matters are the bits set at the end
540 					  of every result set (the EOF packet).
541 					*/
542 					UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, fields_eof.server_status);
543 					if (fields_eof.server_status & SERVER_QUERY_NO_GOOD_INDEX_USED) {
544 						statistic = STAT_BAD_INDEX_USED;
545 					} else if (fields_eof.server_status & SERVER_QUERY_NO_INDEX_USED) {
546 						statistic = STAT_NO_INDEX_USED;
547 					} else if (fields_eof.server_status & SERVER_QUERY_WAS_SLOW) {
548 						statistic = STAT_QUERY_WAS_SLOW;
549 					}
550 					MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
551 				}
552 			} while (0);
553 			PACKET_FREE(&fields_eof);
554 			break; /* switch break */
555 		}
556 	} while (0);
557 	PACKET_FREE(&rset_header);
558 
559 	DBG_INF(ret == PASS? "PASS":"FAIL");
560 	DBG_RETURN(ret);
561 }
562 /* }}} */
563 
564 
565 /* {{{ mysqlnd_result_buffered::fetch_lengths */
566 /*
567   Do lazy initialization for buffered results. As PHP strings have
568   length inside, this function makes not much sense in the context
569   of PHP, to be called as separate function. But let's have it for
570   completeness.
571 */
572 static const size_t *
MYSQLND_METHOD(mysqlnd_result_buffered_zval,fetch_lengths)573 MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_lengths)(const MYSQLND_RES_BUFFERED * const result)
574 {
575 	const MYSQLND_RES_BUFFERED_ZVAL * const set = (const MYSQLND_RES_BUFFERED_ZVAL *) result;
576 	/*
577 	  If:
578 	  - unbuffered result
579 	  - first row has not been read
580 	  - last_row has been read
581 	*/
582 	DBG_ENTER("mysqlnd_result_buffered_zval::fetch_lengths");
583 
584 	if (set->data_cursor == NULL ||
585 		set->data_cursor == set->data ||
586 		((set->data_cursor - set->data) > (result->row_count * result->field_count) ))
587 	{
588 		DBG_INF("EOF");
589 		DBG_RETURN(NULL);/* No rows or no more rows */
590 	}
591 	DBG_INF("non NULL");
592 	DBG_RETURN(result->lengths);
593 }
594 /* }}} */
595 
596 
597 /* {{{ mysqlnd_result_buffered_c::fetch_lengths */
598 /*
599   Do lazy initialization for buffered results. As PHP strings have
600   length inside, this function makes not much sense in the context
601   of PHP, to be called as separate function. But let's have it for
602   completeness.
603 */
604 static const size_t *
MYSQLND_METHOD(mysqlnd_result_buffered_c,fetch_lengths)605 MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_lengths)(const MYSQLND_RES_BUFFERED * const result)
606 {
607 	const MYSQLND_RES_BUFFERED_C * const set = (const MYSQLND_RES_BUFFERED_C *) result;
608 	DBG_ENTER("mysqlnd_result_buffered_c::fetch_lengths");
609 
610 	if (set->current_row > set->row_count || set->current_row == 0) {
611 		DBG_INF("EOF");
612 		DBG_RETURN(NULL); /* No more rows, or no fetched row */
613 	}
614 	DBG_INF("non NULL");
615 	DBG_RETURN(result->lengths);
616 }
617 /* }}} */
618 
619 
620 /* {{{ mysqlnd_result_unbuffered::fetch_lengths */
621 static const size_t *
MYSQLND_METHOD(mysqlnd_result_unbuffered,fetch_lengths)622 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths)(const MYSQLND_RES_UNBUFFERED * const result)
623 {
624 	/* simulate output of libmysql */
625 	return (result->last_row_data || result->eof_reached)? result->lengths : NULL;
626 }
627 /* }}} */
628 
629 
630 /* {{{ mysqlnd_res::fetch_lengths */
631 static const size_t *
MYSQLND_METHOD(mysqlnd_res,fetch_lengths)632 MYSQLND_METHOD(mysqlnd_res, fetch_lengths)(const MYSQLND_RES * const result)
633 {
634 	const size_t * ret;
635 	DBG_ENTER("mysqlnd_res::fetch_lengths");
636 	ret = result->stored_data && result->stored_data->m.fetch_lengths ?
637 					result->stored_data->m.fetch_lengths(result->stored_data) :
638 					(result->unbuf && result->unbuf->m.fetch_lengths ?
639 						result->unbuf->m.fetch_lengths(result->unbuf) :
640 						NULL
641 					);
642 	DBG_RETURN(ret);
643 }
644 /* }}} */
645 
646 
647 /* {{{ mysqlnd_result_unbuffered::fetch_row_c */
648 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_unbuffered,fetch_row_c)649 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row_c)(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything)
650 {
651 	enum_func_status	ret;
652 	MYSQLND_ROW_C		*row = (MYSQLND_ROW_C *) param;
653 	MYSQLND_PACKET_ROW	*row_packet = result->unbuf->row_packet;
654 	MYSQLND_RES_METADATA * const meta = result->meta;
655 	MYSQLND_CONN_DATA * const conn = result->conn;
656 	void *checkpoint;
657 
658 	DBG_ENTER("mysqlnd_result_unbuffered::fetch_row_c");
659 
660 	*fetched_anything = FALSE;
661 	if (result->unbuf->eof_reached) {
662 		/* No more rows obviously */
663 		DBG_RETURN(PASS);
664 	}
665 	if (!conn || GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA) {
666 		SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
667 		DBG_RETURN(FAIL);
668 	}
669 	if (!row_packet) {
670 		/* Not fully initialized object that is being cleaned up */
671 		DBG_RETURN(FAIL);
672 	}
673 	/* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
674 	row_packet->skip_extraction = FALSE;
675 
676 	checkpoint = result->memory_pool->checkpoint;
677 	mysqlnd_mempool_save_state(result->memory_pool);
678 
679 	/*
680 	  If we skip rows (row == NULL) we have to
681 	  result->m.unbuffered_free_last_data() before it. The function returns always true.
682 	*/
683 	if (PASS == (ret = PACKET_READ(conn, row_packet)) && !row_packet->eof) {
684 		result->unbuf->m.free_last_data(result->unbuf, conn->stats);
685 
686 		result->unbuf->last_row_data = row_packet->fields;
687 		result->unbuf->last_row_buffer = row_packet->row_buffer;
688 		row_packet->fields = NULL;
689 		row_packet->row_buffer.ptr = NULL;
690 
691 		MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
692 
693 		if (!row_packet->skip_extraction) {
694 			unsigned int i, field_count = meta->field_count;
695 
696 			enum_func_status rc = result->unbuf->m.row_decoder(&result->unbuf->last_row_buffer,
697 											result->unbuf->last_row_data,
698 											field_count,
699 											row_packet->fields_metadata,
700 											conn->options->int_and_float_native,
701 											conn->stats);
702 			if (PASS != rc) {
703 				mysqlnd_mempool_restore_state(result->memory_pool);
704 				result->memory_pool->checkpoint = checkpoint;
705 				DBG_RETURN(FAIL);
706 			}
707 			{
708 				*row = mnd_malloc(field_count * sizeof(char *));
709 				if (*row) {
710 					MYSQLND_FIELD * field = meta->fields;
711 					size_t * lengths = result->unbuf->lengths;
712 
713 					for (i = 0; i < field_count; i++, field++) {
714 						zval * data = &result->unbuf->last_row_data[i];
715 						const size_t len = (Z_TYPE_P(data) == IS_STRING)? Z_STRLEN_P(data) : 0;
716 
717 /* BEGIN difference between normal normal fetch and _c */
718 						if (Z_TYPE_P(data) != IS_NULL) {
719 							convert_to_string(data);
720 							(*row)[i] = Z_STRVAL_P(data);
721 						} else {
722 							(*row)[i] = NULL;
723 						}
724 /* END difference between normal normal fetch and _c */
725 
726 						if (lengths) {
727 							lengths[i] = len;
728 						}
729 
730 						if (field->max_length < len) {
731 							field->max_length = len;
732 						}
733 					}
734 				} else {
735 					SET_OOM_ERROR(conn->error_info);
736 				}
737 			}
738 		}
739 		result->unbuf->row_count++;
740 		*fetched_anything = TRUE;
741 	} else if (ret == FAIL) {
742 		if (row_packet->error_info.error_no) {
743 			COPY_CLIENT_ERROR(conn->error_info, row_packet->error_info);
744 			DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
745 		}
746 		if (GET_CONNECTION_STATE(&conn->state) != CONN_QUIT_SENT) {
747 			SET_CONNECTION_STATE(&conn->state, CONN_READY);
748 		}
749 		result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
750 	} else if (row_packet->eof) {
751 		/* Mark the connection as usable again */
752 		DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
753 		result->unbuf->eof_reached = TRUE;
754 
755 		UPSERT_STATUS_RESET(conn->upsert_status);
756 		UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
757 		UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
758 		/*
759 		  result->row_packet will be cleaned when
760 		  destroying the result object
761 		*/
762 		if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
763 			SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
764 		} else {
765 			SET_CONNECTION_STATE(&conn->state, CONN_READY);
766 		}
767 		result->unbuf->m.free_last_data(result->unbuf, conn->stats);
768 	}
769 
770 	mysqlnd_mempool_restore_state(result->memory_pool);
771 	result->memory_pool->checkpoint = checkpoint;
772 
773 	DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
774 	DBG_RETURN(PASS);
775 }
776 /* }}} */
777 
778 
779 /* {{{ mysqlnd_result_unbuffered::fetch_row */
780 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_unbuffered,fetch_row)781 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything)
782 {
783 	enum_func_status	ret;
784 	zval				*row = (zval *) param;
785 	MYSQLND_PACKET_ROW	*row_packet = result->unbuf->row_packet;
786 	const MYSQLND_RES_METADATA * const meta = result->meta;
787 	MYSQLND_CONN_DATA * const conn = result->conn;
788 	void *checkpoint;
789 
790 	DBG_ENTER("mysqlnd_result_unbuffered::fetch_row");
791 
792 	*fetched_anything = FALSE;
793 	if (result->unbuf->eof_reached) {
794 		/* No more rows obviously */
795 		DBG_RETURN(PASS);
796 	}
797 	if (GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA) {
798 		SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
799 		DBG_RETURN(FAIL);
800 	}
801 	if (!row_packet) {
802 		/* Not fully initialized object that is being cleaned up */
803 		DBG_RETURN(FAIL);
804 	}
805 	/* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
806 	row_packet->skip_extraction = row? FALSE:TRUE;
807 
808 	checkpoint = result->memory_pool->checkpoint;
809 	mysqlnd_mempool_save_state(result->memory_pool);
810 
811 	/*
812 	  If we skip rows (row == NULL) we have to
813 	  result->m.unbuffered_free_last_data() before it. The function returns always true.
814 	*/
815 	if (PASS == (ret = PACKET_READ(conn, row_packet)) && !row_packet->eof) {
816 		result->unbuf->m.free_last_data(result->unbuf, conn->stats);
817 
818 		result->unbuf->last_row_data = row_packet->fields;
819 		result->unbuf->last_row_buffer = row_packet->row_buffer;
820 		row_packet->fields = NULL;
821 		row_packet->row_buffer.ptr = NULL;
822 
823 		MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
824 
825 		if (!row_packet->skip_extraction) {
826 			unsigned int i, field_count = meta->field_count;
827 
828 			enum_func_status rc = result->unbuf->m.row_decoder(&result->unbuf->last_row_buffer,
829 															   result->unbuf->last_row_data,
830 															   field_count,
831 															   row_packet->fields_metadata,
832 															   conn->options->int_and_float_native,
833 															   conn->stats);
834 			if (PASS != rc) {
835 				mysqlnd_mempool_restore_state(result->memory_pool);
836 				result->memory_pool->checkpoint = checkpoint;
837 				DBG_RETURN(FAIL);
838 			}
839 			{
840 				HashTable * row_ht = Z_ARRVAL_P(row);
841 				MYSQLND_FIELD * field = meta->fields;
842 				size_t * lengths = result->unbuf->lengths;
843 
844 				for (i = 0; i < field_count; i++, field++) {
845 					zval * data = &result->unbuf->last_row_data[i];
846 					const size_t len = (Z_TYPE_P(data) == IS_STRING)? Z_STRLEN_P(data) : 0;
847 
848 					if (flags & MYSQLND_FETCH_NUM) {
849 						if (zend_hash_index_add(row_ht, i, data) != NULL) {
850 							Z_TRY_ADDREF_P(data);
851 						}
852 					}
853 					if (flags & MYSQLND_FETCH_ASSOC) {
854 						/* zend_hash_quick_update needs length + trailing zero */
855 						/* QQ: Error handling ? */
856 						/*
857 						  zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
858 						  the index is a numeric and convert it to it. This however means constant
859 						  hashing of the column name, which is not needed as it can be precomputed.
860 						*/
861 						Z_TRY_ADDREF_P(data);
862 						if (meta->fields[i].is_numeric == FALSE) {
863 							zend_hash_update(row_ht, meta->fields[i].sname, data);
864 						} else {
865 							zend_hash_index_update(row_ht, meta->fields[i].num_key, data);
866 						}
867 					}
868 
869 					if (lengths) {
870 						lengths[i] = len;
871 					}
872 
873 					if (field->max_length < len) {
874 						field->max_length = len;
875 					}
876 				}
877 			}
878 		}
879 		result->unbuf->row_count++;
880 		*fetched_anything = TRUE;
881 	} else if (ret == FAIL) {
882 		if (row_packet->error_info.error_no) {
883 			COPY_CLIENT_ERROR(conn->error_info, row_packet->error_info);
884 			DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
885 		}
886 		if (GET_CONNECTION_STATE(&conn->state) != CONN_QUIT_SENT) {
887 			SET_CONNECTION_STATE(&conn->state, CONN_READY);
888 		}
889 		result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
890 	} else if (row_packet->eof) {
891 		/* Mark the connection as usable again */
892 		DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
893 		result->unbuf->eof_reached = TRUE;
894 
895 		UPSERT_STATUS_RESET(conn->upsert_status);
896 		UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
897 		UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
898 		/*
899 		  result->row_packet will be cleaned when
900 		  destroying the result object
901 		*/
902 		if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
903 			SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
904 		} else {
905 			SET_CONNECTION_STATE(&conn->state, CONN_READY);
906 		}
907 		result->unbuf->m.free_last_data(result->unbuf, conn->stats);
908 	}
909 
910 	mysqlnd_mempool_restore_state(result->memory_pool);
911 	result->memory_pool->checkpoint = checkpoint;
912 
913 	DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
914 	DBG_RETURN(ret);
915 }
916 /* }}} */
917 
918 
919 /* {{{ mysqlnd_res::use_result */
920 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,use_result)921 MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, const zend_bool ps)
922 {
923 	MYSQLND_CONN_DATA * const conn = result->conn;
924 	DBG_ENTER("mysqlnd_res::use_result");
925 
926 	SET_EMPTY_ERROR(conn->error_info);
927 
928 	if (ps == FALSE) {
929 		result->type			= MYSQLND_RES_NORMAL;
930 	} else {
931 		result->type			= MYSQLND_RES_PS_UNBUF;
932 	}
933 
934 	result->unbuf = mysqlnd_result_unbuffered_init(result, result->field_count, ps);
935 	if (!result->unbuf) {
936 		goto oom;
937 	}
938 
939 	/*
940 	  Will be freed in the mysqlnd_internal_free_result_contents() called
941 	  by the resource destructor. mysqlnd_result_unbuffered::fetch_row() expects
942 	  this to be not NULL.
943 	*/
944 	/* FALSE = non-persistent */
945 	{
946 		struct st_mysqlnd_packet_row *row_packet = mnd_emalloc(sizeof(struct st_mysqlnd_packet_row));
947 
948 		conn->payload_decoder_factory->m.init_row_packet(row_packet);
949 		row_packet->result_set_memory_pool = result->unbuf->result_set_memory_pool;
950 		row_packet->field_count = result->field_count;
951 		row_packet->binary_protocol = ps;
952 		row_packet->fields_metadata = result->meta->fields;
953 
954 		result->unbuf->row_packet = row_packet;
955 	}
956 
957 	DBG_RETURN(result);
958 oom:
959 	SET_OOM_ERROR(conn->error_info);
960 	DBG_RETURN(NULL);
961 }
962 /* }}} */
963 
964 
965 /* {{{ mysqlnd_result_buffered::fetch_row_c */
966 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered,fetch_row_c)967 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * const fetched_anything)
968 {
969 	enum_func_status ret = FAIL;
970 	MYSQLND_ROW_C * row = (MYSQLND_ROW_C *) param;
971 	const MYSQLND_RES_METADATA * const meta = result->meta;
972 	const unsigned int field_count = meta->field_count;
973 	MYSQLND_CONN_DATA * const conn = result->conn;
974 	DBG_ENTER("mysqlnd_result_buffered::fetch_row_c");
975 
976 	if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) {
977 		MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
978 
979 		/* If we haven't read everything */
980 		if (set->data_cursor &&
981 			(set->data_cursor - set->data) < (result->stored_data->row_count * field_count))
982 		{
983 			zval *current_row = set->data_cursor;
984 			unsigned int i;
985 
986 			if (Z_ISUNDEF(current_row[0])) {
987 				uint64_t row_num = (set->data_cursor - set->data) / field_count;
988 				enum_func_status rc = set->m.row_decoder(&set->row_buffers[row_num],
989 												current_row,
990 												field_count,
991 												meta->fields,
992 												conn->options->int_and_float_native,
993 												conn->stats);
994 				if (rc != PASS) {
995 					DBG_RETURN(FAIL);
996 				}
997 				++set->initialized_rows;
998 				for (i = 0; i < field_count; ++i) {
999 					/*
1000 					  NULL fields are 0 length, 0 is not more than 0
1001 					  String of zero size, definitely can't be the next max_length.
1002 					  Thus for NULL and zero-length we are quite efficient.
1003 					*/
1004 					if (Z_TYPE(current_row[i]) == IS_STRING) {
1005 						const size_t len = Z_STRLEN(current_row[i]);
1006 						if (meta->fields[i].max_length < len) {
1007 							meta->fields[i].max_length = len;
1008 						}
1009 					}
1010 				}
1011 			}
1012 
1013 /* BEGIN difference between normal normal fetch and _c */
1014 			/* there is no conn handle in this function thus we can't set OOM in error_info */
1015 			*row = mnd_malloc(field_count * sizeof(char *));
1016 			if (*row) {
1017 				for (i = 0; i < field_count; ++i) {
1018 					zval * data = &current_row[i];
1019 
1020 					set->lengths[i] = (Z_TYPE_P(data) == IS_STRING)? Z_STRLEN_P(data) : 0;
1021 
1022 					if (Z_TYPE_P(data) != IS_NULL) {
1023 						convert_to_string(data);
1024 						(*row)[i] = Z_STRVAL_P(data);
1025 					} else {
1026 						(*row)[i] = NULL;
1027 					}
1028 				}
1029 				set->data_cursor += field_count;
1030 				MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
1031 			} else {
1032 				SET_OOM_ERROR(conn->error_info);
1033 			}
1034 /* END difference between normal normal fetch and _c */
1035 
1036 			*fetched_anything = *row? TRUE:FALSE;
1037 			ret = *row? PASS:FAIL;
1038 		} else {
1039 			set->data_cursor = NULL;
1040 			DBG_INF("EOF reached");
1041 			*fetched_anything = FALSE;
1042 			ret = PASS;
1043 		}
1044 	} else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) {
1045 		/*
1046 			We don't support _C with pdo because it uses the data in a different way - just references it.
1047 			We will either leak or give nirvana pointers
1048 		*/
1049 		*fetched_anything = FALSE;
1050 		DBG_RETURN(FAIL);
1051 	}
1052 	DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
1053 	DBG_RETURN(ret);
1054 }
1055 /* }}} */
1056 
1057 
1058 /* {{{ mysqlnd_result_buffered_zval::fetch_row */
1059 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_zval,fetch_row)1060 MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * const fetched_anything)
1061 {
1062 	enum_func_status ret = FAIL;
1063 	zval * row = (zval *) param;
1064 	const MYSQLND_RES_METADATA * const meta = result->meta;
1065 	const unsigned int field_count = meta->field_count;
1066 	MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
1067 	MYSQLND_CONN_DATA * const conn = result->conn;
1068 
1069 	DBG_ENTER("mysqlnd_result_buffered_zval::fetch_row");
1070 
1071 	/* If we haven't read everything */
1072 	if (set->data_cursor && (set->data_cursor - set->data) < (set->row_count * field_count)) {
1073 		unsigned int i;
1074 		zval *current_row = set->data_cursor;
1075 
1076 		if (Z_ISUNDEF(current_row[0])) {
1077 			const size_t row_num = (set->data_cursor - set->data) / field_count;
1078 			enum_func_status rc = set->m.row_decoder(&set->row_buffers[row_num],
1079 													 current_row,
1080 													 field_count,
1081 													 meta->fields,
1082 													 conn->options->int_and_float_native,
1083 													 conn->stats);
1084 			if (rc != PASS) {
1085 				DBG_RETURN(FAIL);
1086 			}
1087 			++set->initialized_rows;
1088 			for (i = 0; i < field_count; ++i) {
1089 				/*
1090 				  NULL fields are 0 length, 0 is not more than 0
1091 				  String of zero size, definitely can't be the next max_length.
1092 				  Thus for NULL and zero-length we are quite efficient.
1093 				*/
1094 				if (Z_TYPE(current_row[i]) == IS_STRING) {
1095 					const size_t len = Z_STRLEN(current_row[i]);
1096 					if (meta->fields[i].max_length < len) {
1097 						meta->fields[i].max_length = len;
1098 					}
1099 				}
1100 			}
1101 		}
1102 
1103 		for (i = 0; i < field_count; ++i) {
1104 			zval * data = &current_row[i];
1105 
1106 			set->lengths[i] = (Z_TYPE_P(data) == IS_STRING)? Z_STRLEN_P(data) : 0;
1107 
1108 			if (flags & MYSQLND_FETCH_NUM) {
1109 				if (zend_hash_index_add(Z_ARRVAL_P(row), i, data) != NULL) {
1110 					Z_TRY_ADDREF_P(data);
1111 				}
1112 			}
1113 			if (flags & MYSQLND_FETCH_ASSOC) {
1114 				/* zend_hash_quick_update needs length + trailing zero */
1115 				/* QQ: Error handling ? */
1116 				/*
1117 				  zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1118 				  the index is a numeric and convert it to it. This however means constant
1119 				  hashing of the column name, which is not needed as it can be precomputed.
1120 				*/
1121 				Z_TRY_ADDREF_P(data);
1122 				if (meta->fields[i].is_numeric == FALSE) {
1123 					zend_hash_update(Z_ARRVAL_P(row), meta->fields[i].sname, data);
1124 				} else {
1125 					zend_hash_index_update(Z_ARRVAL_P(row), meta->fields[i].num_key, data);
1126 				}
1127 			}
1128 		}
1129 		set->data_cursor += field_count;
1130 		MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
1131 		*fetched_anything = TRUE;
1132 		ret = PASS;
1133 	} else {
1134 		set->data_cursor = NULL;
1135 		DBG_INF("EOF reached");
1136 		*fetched_anything = FALSE;
1137 		ret = PASS;
1138 	}
1139 	DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
1140 	DBG_RETURN(ret);
1141 }
1142 /* }}} */
1143 
1144 
1145 /* {{{ mysqlnd_result_buffered_c::fetch_row */
1146 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_c,fetch_row)1147 MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything)
1148 {
1149 	enum_func_status ret = FAIL;
1150 	zval * row = (zval *) param;
1151 	const MYSQLND_RES_METADATA * const meta = result->meta;
1152 	const unsigned int field_count = meta->field_count;
1153 	MYSQLND_CONN_DATA * const conn = result->conn;
1154 
1155 	MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result->stored_data;
1156 
1157 	DBG_ENTER("mysqlnd_result_buffered_c::fetch_row");
1158 
1159 	/* If we haven't read everything */
1160 	if (set->current_row < set->row_count) {
1161 		enum_func_status rc;
1162 		zval * current_row;
1163 		unsigned int i;
1164 
1165 		current_row = mnd_emalloc(field_count * sizeof(zval));
1166 		if (!current_row) {
1167 			SET_OOM_ERROR(conn->error_info);
1168 			DBG_RETURN(FAIL);
1169 		}
1170 
1171 		rc = result->stored_data->m.row_decoder(&result->stored_data->row_buffers[set->current_row],
1172 												current_row,
1173 												field_count,
1174 												meta->fields,
1175 												conn->options->int_and_float_native,
1176 												conn->stats);
1177 		if (rc != PASS) {
1178 			DBG_RETURN(FAIL);
1179 		}
1180 		if (!ZEND_BIT_TEST(set->initialized, set->current_row)) {
1181 			set->initialized[set->current_row >> 3] |= (1 << (set->current_row & 7)); /* mark initialized */
1182 
1183 			++set->initialized_rows;
1184 
1185 			for (i = 0; i < field_count; ++i) {
1186 				/*
1187 				  NULL fields are 0 length, 0 is not more than 0
1188 				  String of zero size, definitely can't be the next max_length.
1189 				  Thus for NULL and zero-length we are quite efficient.
1190 				*/
1191 				if (Z_TYPE(current_row[i]) == IS_STRING) {
1192 					const size_t len = Z_STRLEN(current_row[i]);
1193 					if (meta->fields[i].max_length < len) {
1194 						meta->fields[i].max_length = len;
1195 					}
1196 				}
1197 			}
1198 		}
1199 
1200 		for (i = 0; i < field_count; ++i) {
1201 			zval * data = &current_row[i];
1202 
1203 			set->lengths[i] = (Z_TYPE_P(data) == IS_STRING)? Z_STRLEN_P(data) : 0;
1204 
1205 			if (flags & MYSQLND_FETCH_NUM) {
1206 				if (zend_hash_index_add(Z_ARRVAL_P(row), i, data)) {
1207 					Z_TRY_ADDREF_P(data);
1208 				}
1209 			}
1210 			if (flags & MYSQLND_FETCH_ASSOC) {
1211 				/* zend_hash_quick_update needs length + trailing zero */
1212 				/* QQ: Error handling ? */
1213 				/*
1214 				  zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1215 				  the index is a numeric and convert it to it. This however means constant
1216 				  hashing of the column name, which is not needed as it can be precomputed.
1217 				*/
1218 				Z_TRY_ADDREF_P(data);
1219 				if (meta->fields[i].is_numeric == FALSE) {
1220 					zend_hash_update(Z_ARRVAL_P(row), meta->fields[i].sname, data);
1221 				} else {
1222 					zend_hash_index_update(Z_ARRVAL_P(row), meta->fields[i].num_key, data);
1223 				}
1224 			}
1225 			/*
1226 				This will usually not destroy anything but decref.
1227 				However, if neither NUM nor ASSOC is set we will free memory cleanly and won't leak.
1228 				It also simplifies the handling of Z_ADDREF_P because we don't need to check if only
1229 				either NUM or ASSOC is set but not both.
1230 			*/
1231 			zval_ptr_dtor_nogc(data);
1232 		}
1233 		mnd_efree(current_row);
1234 		++set->current_row;
1235 		MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
1236 		*fetched_anything = TRUE;
1237 		ret = PASS;
1238 	} else {
1239 		if (set->current_row == set->row_count) {
1240 			set->current_row = set->row_count + 1;
1241 		}
1242 		DBG_INF_FMT("EOF reached. current_row=%llu", (unsigned long long) set->current_row);
1243 		*fetched_anything = FALSE;
1244 		ret = PASS;
1245 	}
1246 
1247 	DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
1248 	DBG_RETURN(ret);
1249 }
1250 /* }}} */
1251 
1252 
1253 /* {{{ mysqlnd_res::fetch_row */
1254 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,fetch_row)1255 MYSQLND_METHOD(mysqlnd_res, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool *fetched_anything)
1256 {
1257 	const mysqlnd_fetch_row_func f = result->stored_data? result->stored_data->m.fetch_row:(result->unbuf? result->unbuf->m.fetch_row:NULL);
1258 	if (f) {
1259 		return f(result, param, flags, fetched_anything);
1260 	}
1261 	*fetched_anything = FALSE;
1262 	return PASS;
1263 }
1264 /* }}} */
1265 
1266 
1267 /* {{{ mysqlnd_res::store_result_fetch_data */
1268 enum_func_status
MYSQLND_METHOD(mysqlnd_res,store_result_fetch_data)1269 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result,
1270 													MYSQLND_RES_METADATA * meta,
1271 													MYSQLND_ROW_BUFFER **row_buffers,
1272 													zend_bool binary_protocol)
1273 {
1274 	enum_func_status ret;
1275 	uint64_t total_allocated_rows = 0;
1276 	unsigned int free_rows = 0;
1277 	MYSQLND_RES_BUFFERED * set = result->stored_data;
1278 	MYSQLND_PACKET_ROW row_packet;
1279 
1280 	DBG_ENTER("mysqlnd_res::store_result_fetch_data");
1281 	if (!set || !row_buffers) {
1282 		ret = FAIL;
1283 		goto end;
1284 	}
1285 
1286 	*row_buffers = NULL;
1287 
1288 	conn->payload_decoder_factory->m.init_row_packet(&row_packet);
1289 	set->references	= 1;
1290 
1291 	row_packet.result_set_memory_pool = result->stored_data->result_set_memory_pool;
1292 	row_packet.field_count = meta->field_count;
1293 	row_packet.binary_protocol = binary_protocol;
1294 	row_packet.fields_metadata = meta->fields;
1295 
1296 	row_packet.skip_extraction = TRUE; /* let php_mysqlnd_rowp_read() not allocate row_packet.fields, we will do it */
1297 
1298 	while (FAIL != (ret = PACKET_READ(conn, &row_packet)) && !row_packet.eof) {
1299 		if (!free_rows) {
1300 			MYSQLND_ROW_BUFFER * new_row_buffers;
1301 
1302 			if (total_allocated_rows < 1024) {
1303 				if (total_allocated_rows == 0) {
1304 					free_rows = 1;
1305 					total_allocated_rows = 1;
1306 				} else {
1307 					free_rows = total_allocated_rows;
1308 					total_allocated_rows *= 2;
1309 				}
1310 			} else {
1311 				free_rows = 1024;
1312 				total_allocated_rows += 1024;
1313 			}
1314 
1315 			/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1316 			if (total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER) > SIZE_MAX) {
1317 				SET_OOM_ERROR(conn->error_info);
1318 				ret = FAIL;
1319 				goto free_end;
1320 			}
1321 			if (*row_buffers) {
1322 				new_row_buffers = mnd_erealloc(*row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER)));
1323 			} else {
1324 				new_row_buffers = mnd_emalloc((size_t)(total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER)));
1325 			}
1326 			if (!new_row_buffers) {
1327 				SET_OOM_ERROR(conn->error_info);
1328 				ret = FAIL;
1329 				goto free_end;
1330 			}
1331 			*row_buffers = new_row_buffers;
1332 		}
1333 		free_rows--;
1334 		(*row_buffers)[set->row_count] = row_packet.row_buffer;
1335 
1336 		set->row_count++;
1337 
1338 		/* So row_packet's destructor function won't efree() it */
1339 		row_packet.fields = NULL;
1340 		row_packet.row_buffer.ptr = NULL;
1341 
1342 		/*
1343 		  No need to FREE_ALLOCA as we can reuse the
1344 		  'lengths' and 'fields' arrays. For lengths its absolutely safe.
1345 		  'fields' is reused because the ownership of the strings has been
1346 		  transferred above.
1347 		*/
1348 	}
1349 	/* Overflow ? */
1350 	MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats,
1351 									   binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
1352 														STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
1353 									   set->row_count);
1354 
1355 	/* Finally clean */
1356 	if (row_packet.eof) {
1357 		UPSERT_STATUS_RESET(conn->upsert_status);
1358 		UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet.warning_count);
1359 		UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet.server_status);
1360 	}
1361 
1362 	if (ret == FAIL) {
1363 		/* Error packets do not contain server status information. However, we know that after
1364 		 * an error there will be no further result sets. */
1365 		UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status,
1366 			UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & ~SERVER_MORE_RESULTS_EXISTS);
1367 	}
1368 
1369 	/* save some memory */
1370 	if (free_rows) {
1371 		/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1372 		if (set->row_count * sizeof(MYSQLND_ROW_BUFFER) > SIZE_MAX) {
1373 			SET_OOM_ERROR(conn->error_info);
1374 			ret = FAIL;
1375 			goto free_end;
1376 		}
1377 		*row_buffers = mnd_erealloc(*row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_ROW_BUFFER)));
1378 	}
1379 
1380 	if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
1381 		SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
1382 	} else {
1383 		SET_CONNECTION_STATE(&conn->state, CONN_READY);
1384 	}
1385 
1386 	if (ret == FAIL) {
1387 		COPY_CLIENT_ERROR(&set->error_info, row_packet.error_info);
1388 	} else {
1389 		/* libmysql's documentation says it should be so for SELECT statements */
1390 		UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, set->row_count);
1391 	}
1392 	DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u",
1393 				ret == PASS? "PASS":"FAIL",
1394 				(uint32_t) set->row_count,
1395 				UPSERT_STATUS_GET_WARNINGS(conn->upsert_status),
1396 				UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status));
1397 free_end:
1398 	PACKET_FREE(&row_packet);
1399 end:
1400 	DBG_INF_FMT("rows=%llu", (unsigned long long)result->stored_data->row_count);
1401 	DBG_RETURN(ret);
1402 }
1403 /* }}} */
1404 
1405 
1406 /* {{{ mysqlnd_res::store_result */
1407 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,store_result)1408 MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
1409 										  MYSQLND_CONN_DATA * const conn,
1410 										  const unsigned int flags)
1411 {
1412 	enum_func_status ret;
1413 	MYSQLND_ROW_BUFFER **row_buffers = NULL;
1414 
1415 	DBG_ENTER("mysqlnd_res::store_result");
1416 
1417 	/* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
1418 	/* In case of error the reference will be released in free_result_internal() called indirectly by our caller */
1419 	result->conn = conn->m->get_reference(conn);
1420 	result->type = MYSQLND_RES_NORMAL;
1421 
1422 	SET_CONNECTION_STATE(&conn->state, CONN_FETCHING_DATA);
1423 
1424 	if (flags & MYSQLND_STORE_NO_COPY) {
1425 		result->stored_data	= (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_zval_init(result, result->field_count, flags & MYSQLND_STORE_PS);
1426 		if (!result->stored_data) {
1427 			SET_OOM_ERROR(conn->error_info);
1428 			DBG_RETURN(NULL);
1429 		}
1430 		row_buffers = &result->stored_data->row_buffers;
1431 	} else if (flags & MYSQLND_STORE_COPY) {
1432 		result->stored_data	= (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_c_init(result, result->field_count, flags & MYSQLND_STORE_PS);
1433 		if (!result->stored_data) {
1434 			SET_OOM_ERROR(conn->error_info);
1435 			DBG_RETURN(NULL);
1436 		}
1437 		row_buffers = &result->stored_data->row_buffers;
1438 	}
1439 	ret = result->m.store_result_fetch_data(conn, result, result->meta, row_buffers, flags & MYSQLND_STORE_PS);
1440 
1441 	if (FAIL == ret) {
1442 		if (result->stored_data) {
1443 			COPY_CLIENT_ERROR(conn->error_info, result->stored_data->error_info);
1444 		} else {
1445 			SET_OOM_ERROR(conn->error_info);
1446 		}
1447 		DBG_RETURN(NULL);
1448 	} else {
1449 		if (flags & MYSQLND_STORE_NO_COPY) {
1450 			const MYSQLND_RES_METADATA * const meta = result->meta;
1451 			MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
1452 
1453 			if (set->row_count) {
1454 				/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1455 				if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) {
1456 					SET_OOM_ERROR(conn->error_info);
1457 					DBG_RETURN(NULL);
1458 				}
1459 				/* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */
1460 				set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval)));
1461 				if (!set->data) {
1462 					SET_OOM_ERROR(conn->error_info);
1463 					DBG_RETURN(NULL);
1464 				}
1465 				memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval)));
1466 			}
1467 			/* Position at the first row */
1468 			set->data_cursor = set->data;
1469 		} else if (flags & MYSQLND_STORE_COPY) {
1470 			MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result->stored_data;
1471 			set->current_row = 0;
1472 			set->initialized = mnd_ecalloc((unsigned int) ((set->row_count / 8) + 1), sizeof(zend_uchar)); /* +1 for safety */
1473 		}
1474 	}
1475 
1476 	/* libmysql's documentation says it should be so for SELECT statements */
1477 	UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, result->stored_data->row_count);
1478 
1479 	DBG_RETURN(result);
1480 }
1481 /* }}} */
1482 
1483 
1484 /* {{{ mysqlnd_res::skip_result */
1485 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,skip_result)1486 MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result)
1487 {
1488 	zend_bool fetched_anything;
1489 
1490 	DBG_ENTER("mysqlnd_res::skip_result");
1491 	/*
1492 	  Unbuffered sets
1493 	  A PS could be prepared - there is metadata and thus a stmt->result but the
1494 	  fetch_row function isn't actually set (NULL), thus we have to skip these.
1495 	*/
1496 	if (result->unbuf && !result->unbuf->eof_reached) {
1497 		MYSQLND_CONN_DATA * const conn = result->conn;
1498 		DBG_INF("skipping result");
1499 		/* We have to fetch all data to clean the line */
1500 		MYSQLND_INC_CONN_STATISTIC(conn->stats,
1501 									result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS:
1502 																		STAT_FLUSHED_PS_SETS);
1503 
1504 		while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything)) && fetched_anything == TRUE) {
1505 			/* do nothing */;
1506 		}
1507 	}
1508 	DBG_RETURN(PASS);
1509 }
1510 /* }}} */
1511 
1512 
1513 /* {{{ mysqlnd_res::free_result */
1514 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,free_result)1515 MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, const zend_bool implicit)
1516 {
1517 	DBG_ENTER("mysqlnd_res::free_result");
1518 
1519 	MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL,
1520 							   implicit == TRUE?	STAT_FREE_RESULT_IMPLICIT:
1521 							   						STAT_FREE_RESULT_EXPLICIT);
1522 
1523 	result->m.free_result_internal(result);
1524 	DBG_RETURN(PASS);
1525 }
1526 /* }}} */
1527 
1528 
1529 /* {{{ mysqlnd_res::data_seek */
1530 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,data_seek)1531 MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * const result, const uint64_t row)
1532 {
1533 	DBG_ENTER("mysqlnd_res::data_seek");
1534 	DBG_INF_FMT("row=%lu", row);
1535 
1536 	DBG_RETURN(result->stored_data? result->stored_data->m.data_seek(result->stored_data, row) : FAIL);
1537 }
1538 /* }}} */
1539 
1540 
1541 /* {{{ mysqlnd_result_buffered_zval::data_seek */
1542 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_zval,data_seek)1543 MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row)
1544 {
1545 	MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result;
1546 	DBG_ENTER("mysqlnd_result_buffered_zval::data_seek");
1547 
1548 	/* libmysql just moves to the end, it does traversing of a linked list */
1549 	if (row >= set->row_count) {
1550 		set->data_cursor = NULL;
1551 	} else {
1552 		set->data_cursor = set->data + row * result->field_count;
1553 	}
1554 	DBG_RETURN(PASS);
1555 }
1556 /* }}} */
1557 
1558 
1559 /* {{{ mysqlnd_result_buffered_c::data_seek */
1560 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_c,data_seek)1561 MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row)
1562 {
1563 	MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result;
1564 	DBG_ENTER("mysqlnd_result_buffered_c::data_seek");
1565 
1566 	/* libmysql just moves to the end, it does traversing of a linked list */
1567 	if (row >= set->row_count) {
1568 		set->current_row = set->row_count;
1569 	} else {
1570 		set->current_row = row;
1571 	}
1572 	DBG_RETURN(PASS);
1573 }
1574 /* }}} */
1575 
1576 
1577 /* {{{ mysqlnd_result_unbuffered::num_rows */
1578 static uint64_t
MYSQLND_METHOD(mysqlnd_result_unbuffered,num_rows)1579 MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows)(const MYSQLND_RES_UNBUFFERED * const result)
1580 {
1581 	/* Be compatible with libmysql. We count row_count, but will return 0 */
1582 	return result->eof_reached? result->row_count : 0;
1583 }
1584 /* }}} */
1585 
1586 
1587 /* {{{ mysqlnd_result_buffered::num_rows */
1588 static uint64_t
MYSQLND_METHOD(mysqlnd_result_buffered,num_rows)1589 MYSQLND_METHOD(mysqlnd_result_buffered, num_rows)(const MYSQLND_RES_BUFFERED * const result)
1590 {
1591 	return result->row_count;
1592 }
1593 /* }}} */
1594 
1595 
1596 /* {{{ mysqlnd_res::num_rows */
1597 static uint64_t
MYSQLND_METHOD(mysqlnd_res,num_rows)1598 MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result)
1599 {
1600 	return result->stored_data?
1601 			result->stored_data->m.num_rows(result->stored_data) :
1602 			(result->unbuf? result->unbuf->m.num_rows(result->unbuf) : 0);
1603 }
1604 /* }}} */
1605 
1606 
1607 /* {{{ mysqlnd_res::num_fields */
1608 static unsigned int
MYSQLND_METHOD(mysqlnd_res,num_fields)1609 MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result)
1610 {
1611 	return result->field_count;
1612 }
1613 /* }}} */
1614 
1615 
1616 /* {{{ mysqlnd_res::fetch_field */
1617 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field)1618 MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result)
1619 {
1620 	DBG_ENTER("mysqlnd_res::fetch_field");
1621 	do {
1622 		if (result->meta) {
1623 			/*
1624 			  We optimize the result set, so we don't convert all the data from raw buffer format to
1625 			  zval arrays during store. In the case someone doesn't read all the lines this will
1626 			  save time. However, when a metadata call is done, we need to calculate max_length.
1627 			  We don't have control whether max_length will be used, unfortunately. Otherwise we
1628 			  could have been able to skip that step.
1629 			  Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1630 			  then we can have max_length as dynamic property, which will be calculated during runtime and
1631 			  not during mysqli_fetch_field() time.
1632 			*/
1633 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1634 				const MYSQLND_CONN_DATA * const conn = result->conn;
1635 				DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1636 				/* we have to initialize the rest to get the updated max length */
1637 				if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data,
1638 																			  result->meta,
1639 																			  conn->stats,
1640 																			  conn->options->int_and_float_native))
1641 				{
1642 					break;
1643 				}
1644 			}
1645 			DBG_RETURN(result->meta->m->fetch_field(result->meta));
1646 		}
1647 	} while (0);
1648 	DBG_RETURN(NULL);
1649 }
1650 /* }}} */
1651 
1652 
1653 /* {{{ mysqlnd_res::fetch_field_direct */
1654 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field_direct)1655 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET fieldnr)
1656 {
1657 	DBG_ENTER("mysqlnd_res::fetch_field_direct");
1658 	do {
1659 		if (result->meta) {
1660 			/*
1661 			  We optimize the result set, so we don't convert all the data from raw buffer format to
1662 			  zval arrays during store. In the case someone doesn't read all the lines this will
1663 			  save time. However, when a metadata call is done, we need to calculate max_length.
1664 			  We don't have control whether max_length will be used, unfortunately. Otherwise we
1665 			  could have been able to skip that step.
1666 			  Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1667 			  then we can have max_length as dynamic property, which will be calculated during runtime and
1668 			  not during mysqli_fetch_field_direct() time.
1669 			*/
1670 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1671 				const MYSQLND_CONN_DATA * const conn = result->conn;
1672 				DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1673 				/* we have to initialized the rest to get the updated max length */
1674 				if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data,
1675 																			  result->meta,
1676 																			  conn->stats,
1677 																			  conn->options->int_and_float_native))
1678 				{
1679 					break;
1680 				}
1681 			}
1682 			DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr));
1683 		}
1684 	} while (0);
1685 
1686 	DBG_RETURN(NULL);
1687 }
1688 /* }}} */
1689 
1690 
1691 /* {{{ mysqlnd_res::fetch_field */
1692 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_fields)1693 MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result)
1694 {
1695 	DBG_ENTER("mysqlnd_res::fetch_fields");
1696 	do {
1697 		if (result->meta) {
1698 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1699 				const MYSQLND_CONN_DATA * const conn = result->conn;
1700 				/* we have to initialize the rest to get the updated max length */
1701 				if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data,
1702 																			  result->meta,
1703 																			  conn->stats,
1704 																			  conn->options->int_and_float_native))
1705 				{
1706 					break;
1707 				}
1708 			}
1709 			DBG_RETURN(result->meta->m->fetch_fields(result->meta));
1710 		}
1711 	} while (0);
1712 	DBG_RETURN(NULL);
1713 }
1714 /* }}} */
1715 
1716 
1717 /* {{{ mysqlnd_res::field_seek */
1718 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_seek)1719 MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET field_offset)
1720 {
1721 	return result->meta? result->meta->m->field_seek(result->meta, field_offset) : 0;
1722 }
1723 /* }}} */
1724 
1725 
1726 /* {{{ mysqlnd_res::field_tell */
1727 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_tell)1728 MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result)
1729 {
1730 	return result->meta? result->meta->m->field_tell(result->meta) : 0;
1731 }
1732 /* }}} */
1733 
1734 
1735 /* {{{ mysqlnd_res::fetch_into */
1736 static void
MYSQLND_METHOD(mysqlnd_res,fetch_into)1737 MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, const unsigned int flags,
1738 										zval *return_value,
1739 										enum_mysqlnd_extension extension ZEND_FILE_LINE_DC)
1740 {
1741 	zend_bool fetched_anything;
1742 	unsigned int array_size;
1743 
1744 	DBG_ENTER("mysqlnd_res::fetch_into");
1745 
1746 	/*
1747 	  Hint Zend how many elements we will have in the hash. Thus it won't
1748 	  extend and rehash the hash constantly.
1749 	*/
1750 	array_size = result->field_count;
1751 	if ((flags & (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) == (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) {
1752 		array_size *= 2;
1753 	}
1754 	array_init_size(return_value, array_size);
1755 	if (FAIL == result->m.fetch_row(result, (void *)return_value, flags, &fetched_anything)) {
1756 		php_error_docref(NULL, E_WARNING, "Error while reading a row");
1757 		zend_array_destroy(Z_ARR_P(return_value));
1758 		RETVAL_FALSE;
1759 	} else if (fetched_anything == FALSE) {
1760 		zend_array_destroy(Z_ARR_P(return_value));
1761 		switch (extension) {
1762 			case MYSQLND_MYSQLI:
1763 				RETVAL_NULL();
1764 				break;
1765 			case MYSQLND_MYSQL:
1766 				RETVAL_FALSE;
1767 				break;
1768 			default:exit(0);
1769 		}
1770 	}
1771 	/*
1772 	  return_value is IS_NULL for no more data and an array for data. Thus it's ok
1773 	  to return here.
1774 	*/
1775 	DBG_VOID_RETURN;
1776 }
1777 /* }}} */
1778 
1779 
1780 /* {{{ mysqlnd_res::fetch_row_c */
1781 static MYSQLND_ROW_C
MYSQLND_METHOD(mysqlnd_res,fetch_row_c)1782 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result)
1783 {
1784 	zend_bool fetched_anything;
1785 	MYSQLND_ROW_C ret = NULL;
1786 	DBG_ENTER("mysqlnd_res::fetch_row_c");
1787 
1788 	if (result->stored_data && result->stored_data->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row)) {
1789 		MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything);
1790 	} else if (result->unbuf && result->unbuf->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)) {
1791 		MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything);
1792 	} else {
1793 		ret = NULL;
1794 		php_error_docref(NULL, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers");
1795 	}
1796 	DBG_RETURN(ret);
1797 }
1798 /* }}} */
1799 
1800 
1801 /* {{{ mysqlnd_res::fetch_all */
1802 static void
MYSQLND_METHOD(mysqlnd_res,fetch_all)1803 MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, const unsigned int flags, zval *return_value ZEND_FILE_LINE_DC)
1804 {
1805 	zval  row;
1806 	zend_ulong i = 0;
1807 	MYSQLND_RES_BUFFERED *set = result->stored_data;
1808 
1809 	DBG_ENTER("mysqlnd_res::fetch_all");
1810 
1811 	if ((!result->unbuf && !set)) {
1812 		php_error_docref(NULL, E_WARNING, "fetch_all can be used only with buffered sets");
1813 		if (result->conn) {
1814 			SET_CLIENT_ERROR(result->conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "fetch_all can be used only with buffered sets");
1815 		}
1816 		RETVAL_NULL();
1817 		DBG_VOID_RETURN;
1818 	}
1819 
1820 	/* 4 is a magic value. The cast is safe, if larger then the array will be later extended - no big deal :) */
1821 	array_init_size(return_value, set? (unsigned int) set->row_count : 4);
1822 
1823 	do {
1824 		mysqlnd_fetch_into(result, flags, &row, MYSQLND_MYSQLI);
1825 		if (Z_TYPE(row) != IS_ARRAY) {
1826 			zval_ptr_dtor_nogc(&row);
1827 			break;
1828 		}
1829 		add_index_zval(return_value, i++, &row);
1830 	} while (1);
1831 
1832 	DBG_VOID_RETURN;
1833 }
1834 /* }}} */
1835 
1836 
1837 /* {{{ mysqlnd_res::fetch_field_data */
1838 static void
MYSQLND_METHOD(mysqlnd_res,fetch_field_data)1839 MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, const unsigned int offset, zval *return_value)
1840 {
1841 	zval row;
1842 	zval *entry;
1843 	unsigned int i = 0;
1844 
1845 	DBG_ENTER("mysqlnd_res::fetch_field_data");
1846 	DBG_INF_FMT("offset=%u", offset);
1847 	/*
1848 	  Hint Zend how many elements we will have in the hash. Thus it won't
1849 	  extend and rehash the hash constantly.
1850 	*/
1851 	mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, &row, MYSQLND_MYSQL);
1852 	if (Z_TYPE(row) != IS_ARRAY) {
1853 		zval_ptr_dtor_nogc(&row);
1854 		RETVAL_NULL();
1855 		DBG_VOID_RETURN;
1856 	}
1857 
1858 	zend_hash_internal_pointer_reset(Z_ARRVAL(row));
1859 	while (i++ < offset) {
1860 		zend_hash_move_forward(Z_ARRVAL(row));
1861 	}
1862 
1863 	entry = zend_hash_get_current_data(Z_ARRVAL(row));
1864 
1865 	ZVAL_COPY(return_value, entry);
1866 	zval_ptr_dtor_nogc(&row);
1867 
1868 	DBG_VOID_RETURN;
1869 }
1870 /* }}} */
1871 
1872 
1873 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1874 	MYSQLND_METHOD(mysqlnd_res, fetch_row),
1875 	MYSQLND_METHOD(mysqlnd_res, use_result),
1876 	MYSQLND_METHOD(mysqlnd_res, store_result),
1877 	MYSQLND_METHOD(mysqlnd_res, fetch_into),
1878 	MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1879 	MYSQLND_METHOD(mysqlnd_res, fetch_all),
1880 	MYSQLND_METHOD(mysqlnd_res, fetch_field_data),
1881 	MYSQLND_METHOD(mysqlnd_res, num_rows),
1882 	MYSQLND_METHOD(mysqlnd_res, num_fields),
1883 	MYSQLND_METHOD(mysqlnd_res, skip_result),
1884 	MYSQLND_METHOD(mysqlnd_res, data_seek),
1885 	MYSQLND_METHOD(mysqlnd_res, field_seek),
1886 	MYSQLND_METHOD(mysqlnd_res, field_tell),
1887 	MYSQLND_METHOD(mysqlnd_res, fetch_field),
1888 	MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1889 	MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1890 	MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1891 	MYSQLND_METHOD(mysqlnd_res, fetch_lengths),
1892 	MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1893 	MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1894 	MYSQLND_METHOD(mysqlnd_res, free_result),
1895 	MYSQLND_METHOD(mysqlnd_res, free_result_internal),
1896 	MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal),
1897 	mysqlnd_result_meta_init
1898 MYSQLND_CLASS_METHODS_END;
1899 
1900 
1901 MYSQLND_CLASS_METHODS_START(mysqlnd_result_unbuffered)
1902 	MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row),
1903 	NULL, /* row_decoder */
1904 	MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows),
1905 	MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths),
1906 	MYSQLND_METHOD(mysqlnd_result_unbuffered, free_last_data),
1907 	MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)
1908 MYSQLND_CLASS_METHODS_END;
1909 
1910 
1911 MYSQLND_CLASS_METHODS_START(mysqlnd_result_buffered)
1912 	NULL, /* fetch_row   */
1913 	NULL, /* row_decoder */
1914 	MYSQLND_METHOD(mysqlnd_result_buffered, num_rows),
1915 	NULL, /* fetch_lengths */
1916 	NULL, /* data_seek */
1917 	NULL, /* initialize_result_set_rest */
1918 	MYSQLND_METHOD(mysqlnd_result_buffered, free_result)
1919 MYSQLND_CLASS_METHODS_END;
1920 
1921 
1922 /* {{{ mysqlnd_result_init */
1923 PHPAPI MYSQLND_RES *
mysqlnd_result_init(const unsigned int field_count)1924 mysqlnd_result_init(const unsigned int field_count)
1925 {
1926 	const size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1927 	MYSQLND_MEMORY_POOL * pool;
1928 	MYSQLND_RES * ret;
1929 
1930 	DBG_ENTER("mysqlnd_result_init");
1931 
1932 	pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size));
1933 	if (!pool) {
1934 		DBG_RETURN(NULL);
1935 	}
1936 
1937 	ret = pool->get_chunk(pool, alloc_size);
1938 	memset(ret, 0, alloc_size);
1939 
1940 	ret->memory_pool	= pool;
1941 	ret->field_count	= field_count;
1942 	ret->m = *mysqlnd_result_get_methods();
1943 
1944 	mysqlnd_mempool_save_state(pool);
1945 
1946 	DBG_RETURN(ret);
1947 }
1948 /* }}} */
1949 
1950 
1951 /* {{{ mysqlnd_result_unbuffered_init */
1952 PHPAPI MYSQLND_RES_UNBUFFERED *
mysqlnd_result_unbuffered_init(MYSQLND_RES * result,const unsigned int field_count,const zend_bool ps)1953 mysqlnd_result_unbuffered_init(MYSQLND_RES *result, const unsigned int field_count, const zend_bool ps)
1954 {
1955 	const size_t alloc_size = sizeof(MYSQLND_RES_UNBUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1956 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1957 	MYSQLND_RES_UNBUFFERED * ret;
1958 
1959 	DBG_ENTER("mysqlnd_result_unbuffered_init");
1960 
1961 	ret = pool->get_chunk(pool, alloc_size);
1962 	memset(ret, 0, alloc_size);
1963 
1964 	ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1965 	memset(ret->lengths, 0, field_count * sizeof(size_t));
1966 
1967 	ret->result_set_memory_pool = pool;
1968 	ret->field_count= field_count;
1969 	ret->ps = ps;
1970 
1971 	ret->m = *mysqlnd_result_unbuffered_get_methods();
1972 
1973 	if (ps) {
1974 		ret->m.fetch_lengths = NULL; /* makes no sense */
1975 		ret->m.row_decoder	= php_mysqlnd_rowp_read_binary_protocol;
1976 	} else {
1977 		ret->m.row_decoder	= php_mysqlnd_rowp_read_text_protocol_zval;
1978 	}
1979 
1980 	DBG_RETURN(ret);
1981 }
1982 /* }}} */
1983 
1984 
1985 /* {{{ mysqlnd_result_buffered_zval_init */
1986 PHPAPI MYSQLND_RES_BUFFERED_ZVAL *
mysqlnd_result_buffered_zval_init(MYSQLND_RES * result,const unsigned int field_count,const zend_bool ps)1987 mysqlnd_result_buffered_zval_init(MYSQLND_RES * result, const unsigned int field_count, const zend_bool ps)
1988 {
1989 	const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_ZVAL) + mysqlnd_plugin_count() * sizeof(void *);
1990 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1991 	MYSQLND_RES_BUFFERED_ZVAL * ret;
1992 
1993 	DBG_ENTER("mysqlnd_result_buffered_zval_init");
1994 
1995 	ret = pool->get_chunk(pool, alloc_size);
1996 	memset(ret, 0, alloc_size);
1997 
1998 	if (FAIL == mysqlnd_error_info_init(&ret->error_info, 0)) {
1999 		DBG_RETURN(NULL);
2000 	}
2001 
2002 	ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
2003 	memset(ret->lengths, 0, field_count * sizeof(size_t));
2004 
2005 	ret->result_set_memory_pool = pool;
2006 	ret->field_count= field_count;
2007 	ret->ps = ps;
2008 	ret->m = *mysqlnd_result_buffered_get_methods();
2009 	ret->type = MYSQLND_BUFFERED_TYPE_ZVAL;
2010 
2011 	if (ps) {
2012 		ret->m.fetch_lengths = NULL; /* makes no sense */
2013 		ret->m.row_decoder	= php_mysqlnd_rowp_read_binary_protocol;
2014 	} else {
2015 		ret->m.row_decoder	= php_mysqlnd_rowp_read_text_protocol_zval;
2016 	}
2017 	ret->m.fetch_row		= MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row);
2018 	ret->m.fetch_lengths 	= MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_lengths);
2019 	ret->m.data_seek		= MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek);
2020 	ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_zval, initialize_result_set_rest);
2021 	DBG_RETURN(ret);
2022 }
2023 /* }}} */
2024 
2025 
2026 /* {{{ mysqlnd_result_buffered_c_init */
2027 PHPAPI MYSQLND_RES_BUFFERED_C *
mysqlnd_result_buffered_c_init(MYSQLND_RES * result,const unsigned int field_count,const zend_bool ps)2028 mysqlnd_result_buffered_c_init(MYSQLND_RES * result, const unsigned int field_count, const zend_bool ps)
2029 {
2030 	const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_C) + mysqlnd_plugin_count() * sizeof(void *);
2031 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
2032 	MYSQLND_RES_BUFFERED_C * ret;
2033 
2034 	DBG_ENTER("mysqlnd_result_buffered_c_init");
2035 
2036 	ret = pool->get_chunk(pool, alloc_size);
2037 	memset(ret, 0, alloc_size);
2038 
2039 	if (FAIL == mysqlnd_error_info_init(&ret->error_info, 0)) {
2040 		DBG_RETURN(NULL);
2041 	}
2042 
2043 	ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
2044 	memset(ret->lengths, 0, field_count * sizeof(size_t));
2045 
2046 	ret->result_set_memory_pool = pool;
2047 	ret->field_count= field_count;
2048 	ret->ps = ps;
2049 	ret->m = *mysqlnd_result_buffered_get_methods();
2050 	ret->type = MYSQLND_BUFFERED_TYPE_C;
2051 
2052 	if (ps) {
2053 		ret->m.fetch_lengths = NULL; /* makes no sense */
2054 		ret->m.row_decoder	= php_mysqlnd_rowp_read_binary_protocol;
2055 	} else {
2056 		ret->m.row_decoder	= php_mysqlnd_rowp_read_text_protocol_c;
2057 	}
2058 	ret->m.fetch_row		= MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_row);
2059 	ret->m.fetch_lengths 	= MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_lengths);
2060 	ret->m.data_seek		= MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek);
2061 	ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_c, initialize_result_set_rest);
2062 
2063 	DBG_RETURN(ret);
2064 }
2065 /* }}} */
2066