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