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