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_METHODnull34 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_METHODnull89 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_METHODnull147 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_METHODnull181 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_METHODnull200 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_METHODnull231 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_METHODnull243 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_METHODnull269 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_METHODnull298 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_METHODnull318 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_METHODnull332 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_METHODnull573 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_METHODnull605 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_METHODnull622 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_METHODnull632 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_METHODnull649 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_METHODnull781 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_METHODnull921 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_METHODnull967 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_METHODnull1060 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_METHODnull1147 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_METHODnull1255 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_METHODnull1269 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 	/* save some memory */
1363 	if (free_rows) {
1364 		/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1365 		if (set->row_count * sizeof(MYSQLND_ROW_BUFFER) > SIZE_MAX) {
1366 			SET_OOM_ERROR(conn->error_info);
1367 			ret = FAIL;
1368 			goto free_end;
1369 		}
1370 		*row_buffers = mnd_erealloc(*row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_ROW_BUFFER)));
1371 	}
1372 
1373 	if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
1374 		SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
1375 	} else {
1376 		SET_CONNECTION_STATE(&conn->state, CONN_READY);
1377 	}
1378 
1379 	if (ret == FAIL) {
1380 		COPY_CLIENT_ERROR(&set->error_info, row_packet.error_info);
1381 	} else {
1382 		/* libmysql's documentation says it should be so for SELECT statements */
1383 		UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, set->row_count);
1384 	}
1385 	DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u",
1386 				ret == PASS? "PASS":"FAIL",
1387 				(uint32_t) set->row_count,
1388 				UPSERT_STATUS_GET_WARNINGS(conn->upsert_status),
1389 				UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status));
1390 free_end:
1391 	PACKET_FREE(&row_packet);
1392 end:
1393 	DBG_INF_FMT("rows=%llu", (unsigned long long)result->stored_data->row_count);
1394 	DBG_RETURN(ret);
1395 }
1396 /* }}} */
1397 
1398 
1399 /* {{{ mysqlnd_res::store_result */
1400 static MYSQLND_RES *
MYSQLND_METHODnull1401 MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
1402 										  MYSQLND_CONN_DATA * const conn,
1403 										  const unsigned int flags)
1404 {
1405 	enum_func_status ret;
1406 	MYSQLND_ROW_BUFFER **row_buffers = NULL;
1407 
1408 	DBG_ENTER("mysqlnd_res::store_result");
1409 
1410 	/* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
1411 	/* In case of error the reference will be released in free_result_internal() called indirectly by our caller */
1412 	result->conn = conn->m->get_reference(conn);
1413 	result->type = MYSQLND_RES_NORMAL;
1414 
1415 	SET_CONNECTION_STATE(&conn->state, CONN_FETCHING_DATA);
1416 
1417 	if (flags & MYSQLND_STORE_NO_COPY) {
1418 		result->stored_data	= (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_zval_init(result, result->field_count, flags & MYSQLND_STORE_PS);
1419 		if (!result->stored_data) {
1420 			SET_OOM_ERROR(conn->error_info);
1421 			DBG_RETURN(NULL);
1422 		}
1423 		row_buffers = &result->stored_data->row_buffers;
1424 	} else if (flags & MYSQLND_STORE_COPY) {
1425 		result->stored_data	= (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_c_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 	}
1432 	ret = result->m.store_result_fetch_data(conn, result, result->meta, row_buffers, flags & MYSQLND_STORE_PS);
1433 
1434 	if (FAIL == ret) {
1435 		if (result->stored_data) {
1436 			COPY_CLIENT_ERROR(conn->error_info, result->stored_data->error_info);
1437 		} else {
1438 			SET_OOM_ERROR(conn->error_info);
1439 		}
1440 		DBG_RETURN(NULL);
1441 	} else {
1442 		if (flags & MYSQLND_STORE_NO_COPY) {
1443 			const MYSQLND_RES_METADATA * const meta = result->meta;
1444 			MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
1445 
1446 			if (set->row_count) {
1447 				/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1448 				if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) {
1449 					SET_OOM_ERROR(conn->error_info);
1450 					DBG_RETURN(NULL);
1451 				}
1452 				/* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */
1453 				set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval)));
1454 				if (!set->data) {
1455 					SET_OOM_ERROR(conn->error_info);
1456 					DBG_RETURN(NULL);
1457 				}
1458 				memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval)));
1459 			}
1460 			/* Position at the first row */
1461 			set->data_cursor = set->data;
1462 		} else if (flags & MYSQLND_STORE_COPY) {
1463 			MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result->stored_data;
1464 			set->current_row = 0;
1465 			set->initialized = mnd_ecalloc((unsigned int) ((set->row_count / 8) + 1), sizeof(zend_uchar)); /* +1 for safety */
1466 		}
1467 	}
1468 
1469 	/* libmysql's documentation says it should be so for SELECT statements */
1470 	UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, result->stored_data->row_count);
1471 
1472 	DBG_RETURN(result);
1473 }
1474 /* }}} */
1475 
1476 
1477 /* {{{ mysqlnd_res::skip_result */
1478 static enum_func_status
MYSQLND_METHODnull1479 MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result)
1480 {
1481 	zend_bool fetched_anything;
1482 
1483 	DBG_ENTER("mysqlnd_res::skip_result");
1484 	/*
1485 	  Unbuffered sets
1486 	  A PS could be prepared - there is metadata and thus a stmt->result but the
1487 	  fetch_row function isn't actually set (NULL), thus we have to skip these.
1488 	*/
1489 	if (result->unbuf && !result->unbuf->eof_reached) {
1490 		MYSQLND_CONN_DATA * const conn = result->conn;
1491 		DBG_INF("skipping result");
1492 		/* We have to fetch all data to clean the line */
1493 		MYSQLND_INC_CONN_STATISTIC(conn->stats,
1494 									result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS:
1495 																		STAT_FLUSHED_PS_SETS);
1496 
1497 		while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything)) && fetched_anything == TRUE) {
1498 			/* do nothing */;
1499 		}
1500 	}
1501 	DBG_RETURN(PASS);
1502 }
1503 /* }}} */
1504 
1505 
1506 /* {{{ mysqlnd_res::free_result */
1507 static enum_func_status
MYSQLND_METHODnull1508 MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, const zend_bool implicit)
1509 {
1510 	DBG_ENTER("mysqlnd_res::free_result");
1511 
1512 	MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL,
1513 							   implicit == TRUE?	STAT_FREE_RESULT_IMPLICIT:
1514 							   						STAT_FREE_RESULT_EXPLICIT);
1515 
1516 	result->m.free_result_internal(result);
1517 	DBG_RETURN(PASS);
1518 }
1519 /* }}} */
1520 
1521 
1522 /* {{{ mysqlnd_res::data_seek */
1523 static enum_func_status
MYSQLND_METHODnull1524 MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * const result, const uint64_t row)
1525 {
1526 	DBG_ENTER("mysqlnd_res::data_seek");
1527 	DBG_INF_FMT("row=%lu", row);
1528 
1529 	DBG_RETURN(result->stored_data? result->stored_data->m.data_seek(result->stored_data, row) : FAIL);
1530 }
1531 /* }}} */
1532 
1533 
1534 /* {{{ mysqlnd_result_buffered_zval::data_seek */
1535 static enum_func_status
MYSQLND_METHODnull1536 MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row)
1537 {
1538 	MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result;
1539 	DBG_ENTER("mysqlnd_result_buffered_zval::data_seek");
1540 
1541 	/* libmysql just moves to the end, it does traversing of a linked list */
1542 	if (row >= set->row_count) {
1543 		set->data_cursor = NULL;
1544 	} else {
1545 		set->data_cursor = set->data + row * result->field_count;
1546 	}
1547 	DBG_RETURN(PASS);
1548 }
1549 /* }}} */
1550 
1551 
1552 /* {{{ mysqlnd_result_buffered_c::data_seek */
1553 static enum_func_status
MYSQLND_METHODnull1554 MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row)
1555 {
1556 	MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result;
1557 	DBG_ENTER("mysqlnd_result_buffered_c::data_seek");
1558 
1559 	/* libmysql just moves to the end, it does traversing of a linked list */
1560 	if (row >= set->row_count) {
1561 		set->current_row = set->row_count;
1562 	} else {
1563 		set->current_row = row;
1564 	}
1565 	DBG_RETURN(PASS);
1566 }
1567 /* }}} */
1568 
1569 
1570 /* {{{ mysqlnd_result_unbuffered::num_rows */
1571 static uint64_t
MYSQLND_METHODnull1572 MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows)(const MYSQLND_RES_UNBUFFERED * const result)
1573 {
1574 	/* Be compatible with libmysql. We count row_count, but will return 0 */
1575 	return result->eof_reached? result->row_count : 0;
1576 }
1577 /* }}} */
1578 
1579 
1580 /* {{{ mysqlnd_result_buffered::num_rows */
1581 static uint64_t
MYSQLND_METHODnull1582 MYSQLND_METHOD(mysqlnd_result_buffered, num_rows)(const MYSQLND_RES_BUFFERED * const result)
1583 {
1584 	return result->row_count;
1585 }
1586 /* }}} */
1587 
1588 
1589 /* {{{ mysqlnd_res::num_rows */
1590 static uint64_t
MYSQLND_METHODnull1591 MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result)
1592 {
1593 	return result->stored_data?
1594 			result->stored_data->m.num_rows(result->stored_data) :
1595 			(result->unbuf? result->unbuf->m.num_rows(result->unbuf) : 0);
1596 }
1597 /* }}} */
1598 
1599 
1600 /* {{{ mysqlnd_res::num_fields */
1601 static unsigned int
MYSQLND_METHODnull1602 MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result)
1603 {
1604 	return result->field_count;
1605 }
1606 /* }}} */
1607 
1608 
1609 /* {{{ mysqlnd_res::fetch_field */
1610 static const MYSQLND_FIELD *
MYSQLND_METHODnull1611 MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result)
1612 {
1613 	DBG_ENTER("mysqlnd_res::fetch_field");
1614 	do {
1615 		if (result->meta) {
1616 			/*
1617 			  We optimize the result set, so we don't convert all the data from raw buffer format to
1618 			  zval arrays during store. In the case someone doesn't read all the lines this will
1619 			  save time. However, when a metadata call is done, we need to calculate max_length.
1620 			  We don't have control whether max_length will be used, unfortunately. Otherwise we
1621 			  could have been able to skip that step.
1622 			  Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1623 			  then we can have max_length as dynamic property, which will be calculated during runtime and
1624 			  not during mysqli_fetch_field() time.
1625 			*/
1626 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1627 				const MYSQLND_CONN_DATA * const conn = result->conn;
1628 				DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1629 				/* we have to initialize the rest to get the updated max length */
1630 				if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data,
1631 																			  result->meta,
1632 																			  conn->stats,
1633 																			  conn->options->int_and_float_native))
1634 				{
1635 					break;
1636 				}
1637 			}
1638 			DBG_RETURN(result->meta->m->fetch_field(result->meta));
1639 		}
1640 	} while (0);
1641 	DBG_RETURN(NULL);
1642 }
1643 /* }}} */
1644 
1645 
1646 /* {{{ mysqlnd_res::fetch_field_direct */
1647 static const MYSQLND_FIELD *
MYSQLND_METHODnull1648 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET fieldnr)
1649 {
1650 	DBG_ENTER("mysqlnd_res::fetch_field_direct");
1651 	do {
1652 		if (result->meta) {
1653 			/*
1654 			  We optimize the result set, so we don't convert all the data from raw buffer format to
1655 			  zval arrays during store. In the case someone doesn't read all the lines this will
1656 			  save time. However, when a metadata call is done, we need to calculate max_length.
1657 			  We don't have control whether max_length will be used, unfortunately. Otherwise we
1658 			  could have been able to skip that step.
1659 			  Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1660 			  then we can have max_length as dynamic property, which will be calculated during runtime and
1661 			  not during mysqli_fetch_field_direct() time.
1662 			*/
1663 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1664 				const MYSQLND_CONN_DATA * const conn = result->conn;
1665 				DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1666 				/* we have to initialized the rest to get the updated max length */
1667 				if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data,
1668 																			  result->meta,
1669 																			  conn->stats,
1670 																			  conn->options->int_and_float_native))
1671 				{
1672 					break;
1673 				}
1674 			}
1675 			DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr));
1676 		}
1677 	} while (0);
1678 
1679 	DBG_RETURN(NULL);
1680 }
1681 /* }}} */
1682 
1683 
1684 /* {{{ mysqlnd_res::fetch_field */
1685 static const MYSQLND_FIELD *
MYSQLND_METHODnull1686 MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result)
1687 {
1688 	DBG_ENTER("mysqlnd_res::fetch_fields");
1689 	do {
1690 		if (result->meta) {
1691 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1692 				const MYSQLND_CONN_DATA * const conn = result->conn;
1693 				/* we have to initialize the rest to get the updated max length */
1694 				if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data,
1695 																			  result->meta,
1696 																			  conn->stats,
1697 																			  conn->options->int_and_float_native))
1698 				{
1699 					break;
1700 				}
1701 			}
1702 			DBG_RETURN(result->meta->m->fetch_fields(result->meta));
1703 		}
1704 	} while (0);
1705 	DBG_RETURN(NULL);
1706 }
1707 /* }}} */
1708 
1709 
1710 /* {{{ mysqlnd_res::field_seek */
1711 static MYSQLND_FIELD_OFFSET
MYSQLND_METHODnull1712 MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET field_offset)
1713 {
1714 	return result->meta? result->meta->m->field_seek(result->meta, field_offset) : 0;
1715 }
1716 /* }}} */
1717 
1718 
1719 /* {{{ mysqlnd_res::field_tell */
1720 static MYSQLND_FIELD_OFFSET
MYSQLND_METHODnull1721 MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result)
1722 {
1723 	return result->meta? result->meta->m->field_tell(result->meta) : 0;
1724 }
1725 /* }}} */
1726 
1727 
1728 /* {{{ mysqlnd_res::fetch_into */
1729 static void
MYSQLND_METHODnull1730 MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, const unsigned int flags,
1731 										zval *return_value,
1732 										enum_mysqlnd_extension extension ZEND_FILE_LINE_DC)
1733 {
1734 	zend_bool fetched_anything;
1735 	unsigned int array_size;
1736 
1737 	DBG_ENTER("mysqlnd_res::fetch_into");
1738 
1739 	/*
1740 	  Hint Zend how many elements we will have in the hash. Thus it won't
1741 	  extend and rehash the hash constantly.
1742 	*/
1743 	array_size = result->field_count;
1744 	if ((flags & (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) == (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) {
1745 		array_size *= 2;
1746 	}
1747 	array_init_size(return_value, array_size);
1748 	if (FAIL == result->m.fetch_row(result, (void *)return_value, flags, &fetched_anything)) {
1749 		php_error_docref(NULL, E_WARNING, "Error while reading a row");
1750 		zend_array_destroy(Z_ARR_P(return_value));
1751 		RETVAL_FALSE;
1752 	} else if (fetched_anything == FALSE) {
1753 		zend_array_destroy(Z_ARR_P(return_value));
1754 		switch (extension) {
1755 			case MYSQLND_MYSQLI:
1756 				RETVAL_NULL();
1757 				break;
1758 			case MYSQLND_MYSQL:
1759 				RETVAL_FALSE;
1760 				break;
1761 			default:exit(0);
1762 		}
1763 	}
1764 	/*
1765 	  return_value is IS_NULL for no more data and an array for data. Thus it's ok
1766 	  to return here.
1767 	*/
1768 	DBG_VOID_RETURN;
1769 }
1770 /* }}} */
1771 
1772 
1773 /* {{{ mysqlnd_res::fetch_row_c */
1774 static MYSQLND_ROW_C
MYSQLND_METHODnull1775 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result)
1776 {
1777 	zend_bool fetched_anything;
1778 	MYSQLND_ROW_C ret = NULL;
1779 	DBG_ENTER("mysqlnd_res::fetch_row_c");
1780 
1781 	if (result->stored_data && result->stored_data->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row)) {
1782 		MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything);
1783 	} else if (result->unbuf && result->unbuf->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)) {
1784 		MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything);
1785 	} else {
1786 		ret = NULL;
1787 		php_error_docref(NULL, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers");
1788 	}
1789 	DBG_RETURN(ret);
1790 }
1791 /* }}} */
1792 
1793 
1794 /* {{{ mysqlnd_res::fetch_all */
1795 static void
MYSQLND_METHODnull1796 MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, const unsigned int flags, zval *return_value ZEND_FILE_LINE_DC)
1797 {
1798 	zval  row;
1799 	zend_ulong i = 0;
1800 	MYSQLND_RES_BUFFERED *set = result->stored_data;
1801 
1802 	DBG_ENTER("mysqlnd_res::fetch_all");
1803 
1804 	if ((!result->unbuf && !set)) {
1805 		php_error_docref(NULL, E_WARNING, "fetch_all can be used only with buffered sets");
1806 		if (result->conn) {
1807 			SET_CLIENT_ERROR(result->conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "fetch_all can be used only with buffered sets");
1808 		}
1809 		RETVAL_NULL();
1810 		DBG_VOID_RETURN;
1811 	}
1812 
1813 	/* 4 is a magic value. The cast is safe, if larger then the array will be later extended - no big deal :) */
1814 	array_init_size(return_value, set? (unsigned int) set->row_count : 4);
1815 
1816 	do {
1817 		mysqlnd_fetch_into(result, flags, &row, MYSQLND_MYSQLI);
1818 		if (Z_TYPE(row) != IS_ARRAY) {
1819 			zval_ptr_dtor_nogc(&row);
1820 			break;
1821 		}
1822 		add_index_zval(return_value, i++, &row);
1823 	} while (1);
1824 
1825 	DBG_VOID_RETURN;
1826 }
1827 /* }}} */
1828 
1829 
1830 /* {{{ mysqlnd_res::fetch_field_data */
1831 static void
MYSQLND_METHODnull1832 MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, const unsigned int offset, zval *return_value)
1833 {
1834 	zval row;
1835 	zval *entry;
1836 	unsigned int i = 0;
1837 
1838 	DBG_ENTER("mysqlnd_res::fetch_field_data");
1839 	DBG_INF_FMT("offset=%u", offset);
1840 	/*
1841 	  Hint Zend how many elements we will have in the hash. Thus it won't
1842 	  extend and rehash the hash constantly.
1843 	*/
1844 	mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, &row, MYSQLND_MYSQL);
1845 	if (Z_TYPE(row) != IS_ARRAY) {
1846 		zval_ptr_dtor_nogc(&row);
1847 		RETVAL_NULL();
1848 		DBG_VOID_RETURN;
1849 	}
1850 
1851 	zend_hash_internal_pointer_reset(Z_ARRVAL(row));
1852 	while (i++ < offset) {
1853 		zend_hash_move_forward(Z_ARRVAL(row));
1854 	}
1855 
1856 	entry = zend_hash_get_current_data(Z_ARRVAL(row));
1857 
1858 	ZVAL_COPY(return_value, entry);
1859 	zval_ptr_dtor_nogc(&row);
1860 
1861 	DBG_VOID_RETURN;
1862 }
1863 /* }}} */
1864 
1865 
1866 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1867 	MYSQLND_METHOD(mysqlnd_res, fetch_row),
1868 	MYSQLND_METHOD(mysqlnd_res, use_result),
1869 	MYSQLND_METHOD(mysqlnd_res, store_result),
1870 	MYSQLND_METHOD(mysqlnd_res, fetch_into),
1871 	MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1872 	MYSQLND_METHOD(mysqlnd_res, fetch_all),
1873 	MYSQLND_METHOD(mysqlnd_res, fetch_field_data),
1874 	MYSQLND_METHOD(mysqlnd_res, num_rows),
1875 	MYSQLND_METHOD(mysqlnd_res, num_fields),
1876 	MYSQLND_METHOD(mysqlnd_res, skip_result),
1877 	MYSQLND_METHOD(mysqlnd_res, data_seek),
1878 	MYSQLND_METHOD(mysqlnd_res, field_seek),
1879 	MYSQLND_METHOD(mysqlnd_res, field_tell),
1880 	MYSQLND_METHOD(mysqlnd_res, fetch_field),
1881 	MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1882 	MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1883 	MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1884 	MYSQLND_METHOD(mysqlnd_res, fetch_lengths),
1885 	MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1886 	MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1887 	MYSQLND_METHOD(mysqlnd_res, free_result),
1888 	MYSQLND_METHOD(mysqlnd_res, free_result_internal),
1889 	MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal),
1890 	mysqlnd_result_meta_init
1891 MYSQLND_CLASS_METHODS_END;
1892 
1893 
1894 MYSQLND_CLASS_METHODS_START(mysqlnd_result_unbuffered)
1895 	MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row),
1896 	NULL, /* row_decoder */
1897 	MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows),
1898 	MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths),
1899 	MYSQLND_METHOD(mysqlnd_result_unbuffered, free_last_data),
1900 	MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)
1901 MYSQLND_CLASS_METHODS_END;
1902 
1903 
1904 MYSQLND_CLASS_METHODS_START(mysqlnd_result_buffered)
1905 	NULL, /* fetch_row   */
1906 	NULL, /* row_decoder */
1907 	MYSQLND_METHOD(mysqlnd_result_buffered, num_rows),
1908 	NULL, /* fetch_lengths */
1909 	NULL, /* data_seek */
1910 	NULL, /* initialize_result_set_rest */
1911 	MYSQLND_METHOD(mysqlnd_result_buffered, free_result)
1912 MYSQLND_CLASS_METHODS_END;
1913 
1914 
1915 /* {{{ mysqlnd_result_init */
1916 PHPAPI MYSQLND_RES *
1917 mysqlnd_result_init(const unsigned int field_count)
1918 {
1919 	const size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1920 	MYSQLND_MEMORY_POOL * pool;
1921 	MYSQLND_RES * ret;
1922 
1923 	DBG_ENTER("mysqlnd_result_init");
1924 
1925 	pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size));
1926 	if (!pool) {
1927 		DBG_RETURN(NULL);
1928 	}
1929 
1930 	ret = pool->get_chunk(pool, alloc_size);
1931 	memset(ret, 0, alloc_size);
1932 
1933 	ret->memory_pool	= pool;
1934 	ret->field_count	= field_count;
1935 	ret->m = *mysqlnd_result_get_methods();
1936 
1937 	mysqlnd_mempool_save_state(pool);
1938 
1939 	DBG_RETURN(ret);
1940 }
1941 /* }}} */
1942 
1943 
1944 /* {{{ mysqlnd_result_unbuffered_init */
1945 PHPAPI MYSQLND_RES_UNBUFFERED *
mysqlnd_result_unbuffered_init(MYSQLND_RES *result, const unsigned int field_count, const zend_bool ps)1946 mysqlnd_result_unbuffered_init(MYSQLND_RES *result, const unsigned int field_count, const zend_bool ps)
1947 {
1948 	const size_t alloc_size = sizeof(MYSQLND_RES_UNBUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1949 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1950 	MYSQLND_RES_UNBUFFERED * ret;
1951 
1952 	DBG_ENTER("mysqlnd_result_unbuffered_init");
1953 
1954 	ret = pool->get_chunk(pool, alloc_size);
1955 	memset(ret, 0, alloc_size);
1956 
1957 	ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1958 	memset(ret->lengths, 0, field_count * sizeof(size_t));
1959 
1960 	ret->result_set_memory_pool = pool;
1961 	ret->field_count= field_count;
1962 	ret->ps = ps;
1963 
1964 	ret->m = *mysqlnd_result_unbuffered_get_methods();
1965 
1966 	if (ps) {
1967 		ret->m.fetch_lengths = NULL; /* makes no sense */
1968 		ret->m.row_decoder	= php_mysqlnd_rowp_read_binary_protocol;
1969 	} else {
1970 		ret->m.row_decoder	= php_mysqlnd_rowp_read_text_protocol_zval;
1971 	}
1972 
1973 	DBG_RETURN(ret);
1974 }
1975 /* }}} */
1976 
1977 
1978 /* {{{ mysqlnd_result_buffered_zval_init */
1979 PHPAPI MYSQLND_RES_BUFFERED_ZVAL *
mysqlnd_result_buffered_zval_init(MYSQLND_RES * result, const unsigned int field_count, const zend_bool ps)1980 mysqlnd_result_buffered_zval_init(MYSQLND_RES * result, const unsigned int field_count, const zend_bool ps)
1981 {
1982 	const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_ZVAL) + mysqlnd_plugin_count() * sizeof(void *);
1983 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1984 	MYSQLND_RES_BUFFERED_ZVAL * ret;
1985 
1986 	DBG_ENTER("mysqlnd_result_buffered_zval_init");
1987 
1988 	ret = pool->get_chunk(pool, alloc_size);
1989 	memset(ret, 0, alloc_size);
1990 
1991 	if (FAIL == mysqlnd_error_info_init(&ret->error_info, 0)) {
1992 		DBG_RETURN(NULL);
1993 	}
1994 
1995 	ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1996 	memset(ret->lengths, 0, field_count * sizeof(size_t));
1997 
1998 	ret->result_set_memory_pool = pool;
1999 	ret->field_count= field_count;
2000 	ret->ps = ps;
2001 	ret->m = *mysqlnd_result_buffered_get_methods();
2002 	ret->type = MYSQLND_BUFFERED_TYPE_ZVAL;
2003 
2004 	if (ps) {
2005 		ret->m.fetch_lengths = NULL; /* makes no sense */
2006 		ret->m.row_decoder	= php_mysqlnd_rowp_read_binary_protocol;
2007 	} else {
2008 		ret->m.row_decoder	= php_mysqlnd_rowp_read_text_protocol_zval;
2009 	}
2010 	ret->m.fetch_row		= MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row);
2011 	ret->m.fetch_lengths 	= MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_lengths);
2012 	ret->m.data_seek		= MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek);
2013 	ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_zval, initialize_result_set_rest);
2014 	DBG_RETURN(ret);
2015 }
2016 /* }}} */
2017 
2018 
2019 /* {{{ mysqlnd_result_buffered_c_init */
2020 PHPAPI MYSQLND_RES_BUFFERED_C *
mysqlnd_result_buffered_c_init(MYSQLND_RES * result, const unsigned int field_count, const zend_bool ps)2021 mysqlnd_result_buffered_c_init(MYSQLND_RES * result, const unsigned int field_count, const zend_bool ps)
2022 {
2023 	const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_C) + mysqlnd_plugin_count() * sizeof(void *);
2024 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
2025 	MYSQLND_RES_BUFFERED_C * ret;
2026 
2027 	DBG_ENTER("mysqlnd_result_buffered_c_init");
2028 
2029 	ret = pool->get_chunk(pool, alloc_size);
2030 	memset(ret, 0, alloc_size);
2031 
2032 	if (FAIL == mysqlnd_error_info_init(&ret->error_info, 0)) {
2033 		DBG_RETURN(NULL);
2034 	}
2035 
2036 	ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
2037 	memset(ret->lengths, 0, field_count * sizeof(size_t));
2038 
2039 	ret->result_set_memory_pool = pool;
2040 	ret->field_count= field_count;
2041 	ret->ps = ps;
2042 	ret->m = *mysqlnd_result_buffered_get_methods();
2043 	ret->type = MYSQLND_BUFFERED_TYPE_C;
2044 
2045 	if (ps) {
2046 		ret->m.fetch_lengths = NULL; /* makes no sense */
2047 		ret->m.row_decoder	= php_mysqlnd_rowp_read_binary_protocol;
2048 	} else {
2049 		ret->m.row_decoder	= php_mysqlnd_rowp_read_text_protocol_c;
2050 	}
2051 	ret->m.fetch_row		= MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_row);
2052 	ret->m.fetch_lengths 	= MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_lengths);
2053 	ret->m.data_seek		= MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek);
2054 	ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_c, initialize_result_set_rest);
2055 
2056 	DBG_RETURN(ret);
2057 }
2058 /* }}} */
2059