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