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