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