xref: /PHP-5.3/ext/mysqlnd/mysqlnd_result.c (revision c7b8368e)
1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2006-2013 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: Georg Richter <georg@mysql.com>                             |
16   |          Andrey Hristov <andrey@mysql.com>                           |
17   |          Ulf Wendel <uwendel@mysql.com>                              |
18   +----------------------------------------------------------------------+
19 */
20 
21 /* $Id$ */
22 #include "php.h"
23 #include "mysqlnd.h"
24 #include "mysqlnd_wireprotocol.h"
25 #include "mysqlnd_block_alloc.h"
26 #include "mysqlnd_priv.h"
27 #include "mysqlnd_result.h"
28 #include "mysqlnd_result_meta.h"
29 #include "mysqlnd_statistics.h"
30 #include "mysqlnd_debug.h"
31 
32 #define MYSQLND_SILENT
33 
34 
35 /* {{{ mysqlnd_res::initialize_result_set_rest */
36 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,initialize_result_set_rest)37 MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest)(MYSQLND_RES * const result TSRMLS_DC)
38 {
39 	unsigned int i;
40 	zval **data_cursor = result->stored_data? result->stored_data->data:NULL;
41 	zval **data_begin = result->stored_data? result->stored_data->data:NULL;
42 	unsigned int field_count = result->meta? result->meta->field_count : 0;
43 	uint64_t row_count = result->stored_data? result->stored_data->row_count:0;
44 	enum_func_status ret = PASS;
45 	DBG_ENTER("mysqlnd_res::initialize_result_set_rest");
46 
47 	if (!data_cursor || row_count == result->stored_data->initialized_rows) {
48 		DBG_RETURN(ret);
49 	}
50 	while ((data_cursor - data_begin) < (int)(row_count * field_count)) {
51 		if (NULL == data_cursor[0]) {
52 			enum_func_status rc = result->m.row_decoder(
53 									result->stored_data->row_buffers[(data_cursor - data_begin) / field_count],
54 									data_cursor,
55 									result->meta->field_count,
56 									result->meta->fields,
57 									result->stored_data->persistent,
58 									result->conn->options.numeric_and_datetime_as_unicode,
59 									result->conn->options.int_and_float_native,
60 									result->conn->stats TSRMLS_CC);
61 			if (rc != PASS) {
62 				ret = FAIL;
63 				break;
64 			}
65 			result->stored_data->initialized_rows++;
66 			for (i = 0; i < result->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_P(data_cursor[i]) >= IS_STRING) {
73 					unsigned long len = Z_STRLEN_P(data_cursor[i]);
74 					if (result->meta->fields[i].max_length < len) {
75 						result->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_rset_zval_ptr_dtor */
88 static void
mysqlnd_rset_zval_ptr_dtor(zval ** zv,enum_mysqlnd_res_type type,zend_bool * copy_ctor_called TSRMLS_DC)89 mysqlnd_rset_zval_ptr_dtor(zval **zv, enum_mysqlnd_res_type type, zend_bool * copy_ctor_called TSRMLS_DC)
90 {
91 	DBG_ENTER("mysqlnd_rset_zval_ptr_dtor");
92 	if (!zv || !*zv) {
93 		*copy_ctor_called = FALSE;
94 		DBG_ERR_FMT("zv was NULL");
95 		DBG_VOID_RETURN;
96 	}
97 	/*
98 	  This zval is not from the cache block.
99 	  Thus the refcount is -1 than of a zval from the cache,
100 	  because the zvals from the cache are owned by it.
101 	*/
102 	if (type == MYSQLND_RES_PS_BUF || type == MYSQLND_RES_PS_UNBUF) {
103 		*copy_ctor_called = FALSE;
104 		; /* do nothing, zval_ptr_dtor will do the job*/
105 	} else if (Z_REFCOUNT_PP(zv) > 1) {
106 		/*
107 		  Not a prepared statement, then we have to
108 		  call copy_ctor and then zval_ptr_dtor()
109 
110 		  In Unicode mode the destruction  of the zvals should not call
111 		  zval_copy_ctor() because then we will leak.
112 		  I suppose we can use UG(unicode) in mysqlnd.c when freeing a result set
113 		  to check if we need to call copy_ctor().
114 
115 		  If the type is IS_UNICODE, which can happen with PHP6, then we don't
116 		  need to copy_ctor, as the data doesn't point to our internal buffers.
117 		  If it's string (in PHP5 always) and in PHP6 if data is binary, then
118 		  it still points to internal buffers and has to be copied.
119 		*/
120 		if (Z_TYPE_PP(zv) == IS_STRING) {
121 			zval_copy_ctor(*zv);
122 		}
123 		*copy_ctor_called = TRUE;
124 	} else {
125 		/*
126 		  noone but us point to this, so we can safely ZVAL_NULL the zval,
127 		  so Zend does not try to free what the zval points to - which is
128 		  in result set buffers
129 		*/
130 		*copy_ctor_called = FALSE;
131 		if (Z_TYPE_PP(zv) == IS_STRING) {
132 			ZVAL_NULL(*zv);
133 		}
134 	}
135 	zval_ptr_dtor(zv);
136 	DBG_VOID_RETURN;
137 }
138 /* }}} */
139 
140 
141 /* {{{ mysqlnd_res::unbuffered_free_last_data */
142 static void
MYSQLND_METHOD(mysqlnd_res,unbuffered_free_last_data)143 MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data)(MYSQLND_RES * result TSRMLS_DC)
144 {
145 	MYSQLND_RES_UNBUFFERED *unbuf = result->unbuf;
146 
147 	DBG_ENTER("mysqlnd_res::unbuffered_free_last_data");
148 
149 	if (!unbuf) {
150 		DBG_VOID_RETURN;
151 	}
152 
153 	DBG_INF_FMT("last_row_data=%p", unbuf->last_row_data);
154 	if (unbuf->last_row_data) {
155 		unsigned int i, ctor_called_count = 0;
156 		zend_bool copy_ctor_called;
157 		MYSQLND_STATS *global_stats = result->conn? result->conn->stats:NULL;
158 
159 		DBG_INF_FMT("%u columns to free", result->field_count);
160 		for (i = 0; i < result->field_count; i++) {
161 			mysqlnd_rset_zval_ptr_dtor(&(unbuf->last_row_data[i]), result->type, &copy_ctor_called TSRMLS_CC);
162 			if (copy_ctor_called) {
163 				++ctor_called_count;
164 			}
165 		}
166 		DBG_INF_FMT("copy_ctor_called_count=%u", ctor_called_count);
167 		/* By using value3 macros we hold a mutex only once, there is no value2 */
168 		MYSQLND_INC_CONN_STATISTIC_W_VALUE2(global_stats,
169 											STAT_COPY_ON_WRITE_PERFORMED,
170 											ctor_called_count,
171 											STAT_COPY_ON_WRITE_SAVED,
172 											result->field_count - ctor_called_count);
173 		/* Free last row's zvals */
174 		mnd_efree(unbuf->last_row_data);
175 		unbuf->last_row_data = NULL;
176 	}
177 	if (unbuf->last_row_buffer) {
178 		DBG_INF("Freeing last row buffer");
179 		/* Nothing points to this buffer now, free it */
180 		unbuf->last_row_buffer->free_chunk(unbuf->last_row_buffer TSRMLS_CC);
181 		unbuf->last_row_buffer = NULL;
182 	}
183 
184 	DBG_VOID_RETURN;
185 }
186 /* }}} */
187 
188 
189 /* {{{ mysqlnd_res::free_buffered_data */
190 static void
MYSQLND_METHOD(mysqlnd_res,free_buffered_data)191 MYSQLND_METHOD(mysqlnd_res, free_buffered_data)(MYSQLND_RES * result TSRMLS_DC)
192 {
193 	MYSQLND_RES_BUFFERED *set = result->stored_data;
194 	unsigned int field_count = result->field_count;
195 	int64_t row;
196 
197 	DBG_ENTER("mysqlnd_res::free_buffered_data");
198 	DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count);
199 
200 	DBG_INF("Freeing data & row_buffer");
201 	if (set->data) {
202 		unsigned int copy_on_write_performed = 0;
203 		unsigned int copy_on_write_saved = 0;
204 
205 		DBG_INF_FMT("before: real_usage=%lu  usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC));
206 		for (row = set->row_count - 1; row >= 0; row--) {
207 			zval **current_row = set->data + row * field_count;
208 			MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row];
209 			int64_t col;
210 
211 			if (current_row != NULL) {
212 				for (col = field_count - 1; col >= 0; --col) {
213 					if (current_row[col]) {
214 						zend_bool copy_ctor_called;
215 						mysqlnd_rset_zval_ptr_dtor(&(current_row[col]), result->type, &copy_ctor_called TSRMLS_CC);
216 #if MYSQLND_DEBUG_MEMORY
217 						DBG_INF_FMT("Copy_ctor_called=%u", copy_ctor_called);
218 #endif
219 						if (copy_ctor_called) {
220 							++copy_on_write_performed;
221 						} else {
222 							++copy_on_write_saved;
223 						}
224 					}
225 				}
226 			}
227 #if MYSQLND_DEBUG_MEMORY
228 			DBG_INF("Freeing current_row & current_buffer");
229 #endif
230 			current_buffer->free_chunk(current_buffer TSRMLS_CC);
231 		}
232 
233 		MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_COPY_ON_WRITE_PERFORMED, copy_on_write_performed,
234 											  STAT_COPY_ON_WRITE_SAVED, copy_on_write_saved);
235 		mnd_pefree(set->data, set->persistent);
236 		set->data = NULL;
237 	}
238 
239 	if (set->row_buffers) {
240 		mnd_pefree(set->row_buffers, set->persistent);
241 		set->row_buffers	= NULL;
242 	}
243 	set->data_cursor = NULL;
244 	set->row_count	= 0;
245 
246 	DBG_INF("Freeing set");
247 	mnd_pefree(set, set->persistent);
248 
249 	DBG_INF_FMT("after: real_usage=%lu  usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC));
250 	DBG_VOID_RETURN;
251 }
252 /* }}} */
253 
254 
255 /* {{{ mysqlnd_res::free_result_buffers */
256 static void
MYSQLND_METHOD(mysqlnd_res,free_result_buffers)257 MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES * result TSRMLS_DC)
258 {
259 	DBG_ENTER("mysqlnd_res::free_result_buffers");
260 	DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown"));
261 
262 	if (result->unbuf) {
263 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
264 		mnd_efree(result->unbuf);
265 		result->unbuf = NULL;
266 	} else if (result->stored_data) {
267 		result->m.free_buffered_data(result TSRMLS_CC);
268 		result->stored_data = NULL;
269 	}
270 
271 	if (result->lengths) {
272 		mnd_efree(result->lengths);
273 		result->lengths = NULL;
274 	}
275 
276 	if (result->row_packet) {
277 		DBG_INF("Freeing packet");
278 		PACKET_FREE(result->row_packet);
279 		result->row_packet = NULL;
280 	}
281 
282 	if (result->result_set_memory_pool) {
283 		DBG_INF("Freeing memory pool");
284 		mysqlnd_mempool_destroy(result->result_set_memory_pool TSRMLS_CC);
285 		result->result_set_memory_pool = NULL;
286 	}
287 
288 	DBG_VOID_RETURN;
289 }
290 /* }}} */
291 
292 
293 /* {{{ mysqlnd_internal_free_result_contents */
294 static
mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC)295 void mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC)
296 {
297 	DBG_ENTER("mysqlnd_internal_free_result_contents");
298 
299 	result->m.free_result_buffers(result TSRMLS_CC);
300 
301 	if (result->meta) {
302 		result->meta->m->free_metadata(result->meta TSRMLS_CC);
303 		result->meta = NULL;
304 	}
305 
306 	DBG_VOID_RETURN;
307 }
308 /* }}} */
309 
310 
311 /* {{{ mysqlnd_internal_free_result */
312 static
mysqlnd_internal_free_result(MYSQLND_RES * result TSRMLS_DC)313 void mysqlnd_internal_free_result(MYSQLND_RES * result TSRMLS_DC)
314 {
315 	DBG_ENTER("mysqlnd_internal_free_result");
316 	result->m.free_result_contents(result TSRMLS_CC);
317 
318 	if (result->conn) {
319 		result->conn->m->free_reference(result->conn TSRMLS_CC);
320 		result->conn = NULL;
321 	}
322 
323 	mnd_pefree(result, result->persistent);
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 TSRMLS_DC)
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 TSRMLS_CC);
344 		result->meta = NULL;
345 	}
346 
347 	result->meta = result->m.result_meta_init(result->field_count, result->persistent TSRMLS_CC);
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 TSRMLS_CC)) {
357 		result->m.free_result_contents(result TSRMLS_CC);
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,MYSQLND_STMT * s TSRMLS_DC)377 mysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT * s TSRMLS_DC)
378 {
379 	MYSQLND_STMT_DATA * stmt = s ? s->data:NULL;
380 	enum_func_status ret;
381 	MYSQLND_PACKET_RSET_HEADER * rset_header = NULL;
382 	MYSQLND_PACKET_EOF * fields_eof = NULL;
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 		rset_header = conn->protocol->m.get_rset_header_packet(conn->protocol, FALSE TSRMLS_CC);
390 		if (!rset_header) {
391 			SET_OOM_ERROR(conn->error_info);
392 			ret = FAIL;
393 			break;
394 		}
395 
396 		SET_ERROR_AFF_ROWS(conn);
397 
398 		if (FAIL == (ret = PACKET_READ(rset_header, conn))) {
399 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error reading result set's header");
400 			break;
401 		}
402 
403 		if (rset_header->error_info.error_no) {
404 			/*
405 			  Cover a protocol design error: error packet does not
406 			  contain the server status. Therefore, the client has no way
407 			  to find out whether there are more result sets of
408 			  a multiple-result-set statement pending. Luckily, in 5.0 an
409 			  error always aborts execution of a statement, wherever it is
410 			  a multi-statement or a stored procedure, so it should be
411 			  safe to unconditionally turn off the flag here.
412 			*/
413 			conn->upsert_status.server_status &= ~SERVER_MORE_RESULTS_EXISTS;
414 			/*
415 			  This will copy the error code and the messages, as they
416 			  are buffers in the struct
417 			*/
418 			conn->error_info = rset_header->error_info;
419 			ret = FAIL;
420 			DBG_ERR_FMT("error=%s", rset_header->error_info.error);
421 			/* Return back from CONN_QUERY_SENT */
422 			CONN_SET_STATE(conn, CONN_READY);
423 			break;
424 		}
425 		conn->error_info.error_no = 0;
426 
427 		switch (rset_header->field_count) {
428 			case MYSQLND_NULL_LENGTH: {	/* LOAD DATA LOCAL INFILE */
429 				zend_bool is_warning;
430 				DBG_INF("LOAD DATA");
431 				conn->last_query_type = QUERY_LOAD_LOCAL;
432 				conn->field_count = 0; /* overwrite previous value, or the last value could be used and lead to bug#53503 */
433 				CONN_SET_STATE(conn, CONN_SENDING_LOAD_DATA);
434 				ret = mysqlnd_handle_local_infile(conn, rset_header->info_or_local_file, &is_warning TSRMLS_CC);
435 				CONN_SET_STATE(conn,  (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT);
436 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
437 				break;
438 			}
439 			case 0:				/* UPSERT */
440 				DBG_INF("UPSERT");
441 				conn->last_query_type = QUERY_UPSERT;
442 				conn->field_count = rset_header->field_count;
443 				conn->upsert_status.warning_count = rset_header->warning_count;
444 				conn->upsert_status.server_status = rset_header->server_status;
445 				conn->upsert_status.affected_rows = rset_header->affected_rows;
446 				conn->upsert_status.last_insert_id = rset_header->last_insert_id;
447 				SET_NEW_MESSAGE(conn->last_message, conn->last_message_len,
448 								rset_header->info_or_local_file, rset_header->info_or_local_file_len,
449 								conn->persistent);
450 				/* Result set can follow UPSERT statement, check server_status */
451 				if (conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
452 					CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
453 				} else {
454 					CONN_SET_STATE(conn, CONN_READY);
455 				}
456 				ret = PASS;
457 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
458 				break;
459 			default: do {			/* Result set */
460 				MYSQLND_RES * result;
461 				enum_mysqlnd_collected_stats statistic = STAT_LAST;
462 
463 				DBG_INF("Result set pending");
464 				SET_EMPTY_MESSAGE(conn->last_message, conn->last_message_len, conn->persistent);
465 
466 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_RSET_QUERY);
467 				memset(&conn->upsert_status, 0, sizeof(conn->upsert_status));
468 				/* restore after zeroing */
469 				SET_ERROR_AFF_ROWS(conn);
470 
471 				conn->last_query_type = QUERY_SELECT;
472 				CONN_SET_STATE(conn, CONN_FETCHING_DATA);
473 				/* PS has already allocated it */
474 				conn->field_count = rset_header->field_count;
475 				if (!stmt) {
476 					result = conn->current_result = conn->m->result_init(rset_header->field_count, conn->persistent TSRMLS_CC);
477 				} else {
478 					if (!stmt->result) {
479 						DBG_INF("This is 'SHOW'/'EXPLAIN'-like query.");
480 						/*
481 						  This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
482 						  prepared statements can't send result set metadata for these queries
483 						  on prepare stage. Read it now.
484 						*/
485 						result = stmt->result = conn->m->result_init(rset_header->field_count, stmt->persistent TSRMLS_CC);
486 					} else {
487 						/*
488 						  Update result set metadata if it for some reason changed between
489 						  prepare and execute, i.e.:
490 						  - in case of 'SELECT ?' we don't know column type unless data was
491 							supplied to mysql_stmt_execute, so updated column type is sent
492 							now.
493 						  - if data dictionary changed between prepare and execute, for
494 							example a table used in the query was altered.
495 						  Note, that now (4.1.3) we always send metadata in reply to
496 						  COM_STMT_EXECUTE (even if it is not necessary), so either this or
497 						  previous branch always works.
498 						*/
499 					}
500 					result = stmt->result;
501 				}
502 				if (!result) {
503 					SET_OOM_ERROR(conn->error_info);
504 					ret = FAIL;
505 					break;
506 				}
507 
508 				if (FAIL == (ret = result->m.read_result_metadata(result, conn TSRMLS_CC))) {
509 					/* For PS, we leave them in Prepared state */
510 					if (!stmt && conn->current_result) {
511 						mnd_efree(conn->current_result);
512 						conn->current_result = NULL;
513 					}
514 					DBG_ERR("Error ocurred while reading metadata");
515 					break;
516 				}
517 
518 				/* Check for SERVER_STATUS_MORE_RESULTS if needed */
519 				fields_eof = conn->protocol->m.get_eof_packet(conn->protocol, FALSE TSRMLS_CC);
520 				if (!fields_eof) {
521 					SET_OOM_ERROR(conn->error_info);
522 					ret = FAIL;
523 					break;
524 				}
525 				if (FAIL == (ret = PACKET_READ(fields_eof, conn))) {
526 					DBG_ERR("Error ocurred while reading the EOF packet");
527 					result->m.free_result_contents(result TSRMLS_CC);
528 					mnd_efree(result);
529 					if (!stmt) {
530 						conn->current_result = NULL;
531 					} else {
532 						stmt->result = NULL;
533 						memset(stmt, 0, sizeof(MYSQLND_STMT));
534 						stmt->state = MYSQLND_STMT_INITTED;
535 					}
536 				} else {
537 					unsigned int to_log = MYSQLND_G(log_mask);
538 					to_log &= fields_eof->server_status;
539 					DBG_INF_FMT("warnings=%u server_status=%u", fields_eof->warning_count, fields_eof->server_status);
540 					conn->upsert_status.warning_count = fields_eof->warning_count;
541 					/*
542 					  If SERVER_MORE_RESULTS_EXISTS is set then this is either MULTI_QUERY or a CALL()
543 					  The first packet after sending the query/com_execute has the bit set only
544 					  in this cases. Not sure why it's a needed but it marks that the whole stream
545 					  will include many result sets. What actually matters are the bits set at the end
546 					  of every result set (the EOF packet).
547 					*/
548 					conn->upsert_status.server_status = fields_eof->server_status;
549 					if (fields_eof->server_status & SERVER_QUERY_NO_GOOD_INDEX_USED) {
550 						statistic = STAT_BAD_INDEX_USED;
551 					} else if (fields_eof->server_status & SERVER_QUERY_NO_INDEX_USED) {
552 						statistic = STAT_NO_INDEX_USED;
553 					} else if (fields_eof->server_status & SERVER_QUERY_WAS_SLOW) {
554 						statistic = STAT_QUERY_WAS_SLOW;
555 					}
556 					if (to_log) {
557 #if A0
558 						char *backtrace = mysqlnd_get_backtrace(TSRMLS_C);
559 						php_log_err(backtrace TSRMLS_CC);
560 						efree(backtrace);
561 #endif
562 					}
563 					MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
564 				}
565 			} while (0);
566 			PACKET_FREE(fields_eof);
567 			break; /* switch break */
568 		}
569 	} while (0);
570 	PACKET_FREE(rset_header);
571 
572 	DBG_INF(ret == PASS? "PASS":"FAIL");
573 	DBG_RETURN(ret);
574 }
575 /* }}} */
576 
577 
578 /* {{{ mysqlnd_fetch_lengths_buffered */
579 /*
580   Do lazy initialization for buffered results. As PHP strings have
581   length inside, this function makes not much sense in the context
582   of PHP, to be called as separate function. But let's have it for
583   completeness.
584 */
585 static unsigned long *
mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result TSRMLS_DC)586 mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result TSRMLS_DC)
587 {
588 	unsigned int i;
589 	zval **previous_row;
590 	MYSQLND_RES_BUFFERED *set = result->stored_data;
591 
592 	/*
593 	  If:
594 	  - unbuffered result
595 	  - first row has not been read
596 	  - last_row has been read
597 	*/
598 	if (set->data_cursor == NULL ||
599 		set->data_cursor == set->data ||
600 		((set->data_cursor - set->data) > (set->row_count * result->meta->field_count) ))
601 	{
602 		return NULL;/* No rows or no more rows */
603 	}
604 
605 	previous_row = set->data_cursor - result->meta->field_count;
606 	for (i = 0; i < result->meta->field_count; i++) {
607 		result->lengths[i] = (Z_TYPE_P(previous_row[i]) == IS_NULL)? 0:Z_STRLEN_P(previous_row[i]);
608 	}
609 
610 	return result->lengths;
611 }
612 /* }}} */
613 
614 
615 /* {{{ mysqlnd_fetch_lengths_unbuffered */
616 static unsigned long *
mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result TSRMLS_DC)617 mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result TSRMLS_DC)
618 {
619 	/* simulate output of libmysql */
620 	return (!result->unbuf || result->unbuf->last_row_data || result->unbuf->eof_reached)? result->lengths:NULL;
621 }
622 /* }}} */
623 
624 
625 /* {{{ mysqlnd_res::fetch_lengths */
_mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC)626 PHPAPI unsigned long * _mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC)
627 {
628 	return result->m.fetch_lengths? result->m.fetch_lengths(result TSRMLS_CC) : NULL;
629 }
630 /* }}} */
631 
632 
633 /* {{{ mysqlnd_fetch_row_unbuffered_c */
634 static MYSQLND_ROW_C
mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC)635 mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC)
636 {
637 	enum_func_status	ret;
638 	MYSQLND_ROW_C		retrow = NULL;
639 	unsigned int		i,
640 						field_count = result->field_count;
641 	MYSQLND_PACKET_ROW	*row_packet = result->row_packet;
642 	unsigned long		*lengths = result->lengths;
643 
644 	DBG_ENTER("mysqlnd_fetch_row_unbuffered_c");
645 
646 	if (result->unbuf->eof_reached) {
647 		/* No more rows obviously */
648 		DBG_RETURN(retrow);
649 	}
650 	if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
651 		SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
652 						 UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
653 		DBG_RETURN(retrow);
654 	}
655 	if (!row_packet) {
656 		/* Not fully initialized object that is being cleaned up */
657 		DBG_RETURN(retrow);
658 	}
659 	/* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
660 	row_packet->skip_extraction = FALSE;
661 
662 	/*
663 	  If we skip rows (row == NULL) we have to
664 	  result->m.unbuffered_free_last_data() before it. The function returns always true.
665 	*/
666 	if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
667 		result->unbuf->row_count++;
668 
669 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
670 
671 		result->unbuf->last_row_data = row_packet->fields;
672 		result->unbuf->last_row_buffer = row_packet->row_buffer;
673 		row_packet->fields = NULL;
674 		row_packet->row_buffer = NULL;
675 
676 		MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
677 
678 		if (!row_packet->skip_extraction) {
679 			MYSQLND_FIELD *field = result->meta->fields;
680 			struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
681 
682 			enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer,
683 										  result->unbuf->last_row_data,
684 										  row_packet->field_count,
685 										  row_packet->fields_metadata,
686 										  FALSE,
687 										  result->conn->options.numeric_and_datetime_as_unicode,
688 										  result->conn->options.int_and_float_native,
689 										  result->conn->stats TSRMLS_CC);
690 			if (PASS != rc) {
691 				DBG_RETURN(retrow);
692 			}
693 
694 			retrow = mnd_malloc(result->field_count * sizeof(char *));
695 			if (retrow) {
696 				for (i = 0; i < field_count; i++, field++, hash_key++) {
697 					zval *data = result->unbuf->last_row_data[i];
698 					unsigned int len;
699 
700 					if (Z_TYPE_P(data) != IS_NULL) {
701 						convert_to_string(data);
702 						retrow[i] = Z_STRVAL_P(data);
703 						len = Z_STRLEN_P(data);
704 					} else {
705 						retrow[i] = NULL;
706 						len = 0;
707 					}
708 
709 					if (lengths) {
710 						lengths[i] = len;
711 					}
712 
713 					if (field->max_length < len) {
714 						field->max_length = len;
715 					}
716 				}
717 			} else {
718 				SET_OOM_ERROR(result->conn->error_info);
719 			}
720 		}
721 	} else if (ret == FAIL) {
722 		if (row_packet->error_info.error_no) {
723 			result->conn->error_info = row_packet->error_info;
724 			DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
725 		}
726 		CONN_SET_STATE(result->conn, CONN_READY);
727 		result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
728 	} else if (row_packet->eof) {
729 		/* Mark the connection as usable again */
730 		DBG_INF_FMT("warningss=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
731 		result->unbuf->eof_reached = TRUE;
732 		result->conn->upsert_status.warning_count = row_packet->warning_count;
733 		result->conn->upsert_status.server_status = row_packet->server_status;
734 		/*
735 		  result->row_packet will be cleaned when
736 		  destroying the result object
737 		*/
738 		if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
739 			CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
740 		} else {
741 			CONN_SET_STATE(result->conn, CONN_READY);
742 		}
743 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
744 	}
745 
746 	DBG_RETURN(retrow);
747 }
748 /* }}} */
749 
750 
751 /* {{{ mysqlnd_fetch_row_unbuffered */
752 static enum_func_status
mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)753 mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
754 {
755 	enum_func_status	ret;
756 	zval				*row = (zval *) param;
757 	MYSQLND_PACKET_ROW	*row_packet = result->row_packet;
758 
759 	DBG_ENTER("mysqlnd_fetch_row_unbuffered");
760 	DBG_INF_FMT("flags=%u", flags);
761 
762 	*fetched_anything = FALSE;
763 	if (result->unbuf->eof_reached) {
764 		/* No more rows obviously */
765 		DBG_RETURN(PASS);
766 	}
767 	if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
768 		SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
769 		DBG_RETURN(FAIL);
770 	}
771 	if (!row_packet) {
772 		/* Not fully initialized object that is being cleaned up */
773 		DBG_RETURN(FAIL);
774 	}
775 	/* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
776 	row_packet->skip_extraction = row? FALSE:TRUE;
777 
778 	/*
779 	  If we skip rows (row == NULL) we have to
780 	  result->m.unbuffered_free_last_data() before it. The function returns always true.
781 	*/
782 	if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
783 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
784 
785 		result->unbuf->last_row_data = row_packet->fields;
786 		result->unbuf->last_row_buffer = row_packet->row_buffer;
787 		row_packet->fields = NULL;
788 		row_packet->row_buffer = NULL;
789 
790 		MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
791 
792 		if (!row_packet->skip_extraction) {
793 			HashTable *row_ht = Z_ARRVAL_P(row);
794 			MYSQLND_FIELD *field = result->meta->fields;
795 			struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
796 			unsigned int i, field_count = result->field_count;
797 			unsigned long *lengths = result->lengths;
798 
799 			enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer,
800 											result->unbuf->last_row_data,
801 											field_count,
802 											row_packet->fields_metadata,
803 											FALSE,
804 											result->conn->options.numeric_and_datetime_as_unicode,
805 											result->conn->options.int_and_float_native,
806 											result->conn->stats TSRMLS_CC);
807 			if (PASS != rc) {
808 				DBG_RETURN(FAIL);
809 			}
810 			for (i = 0; i < field_count; i++, field++, hash_key++) {
811 				zval *data = result->unbuf->last_row_data[i];
812 				unsigned int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data);
813 
814 				if (lengths) {
815 					lengths[i] = len;
816 				}
817 
818 				if (flags & MYSQLND_FETCH_NUM) {
819 					Z_ADDREF_P(data);
820 					zend_hash_next_index_insert(row_ht, &data, sizeof(zval *), NULL);
821 				}
822 				if (flags & MYSQLND_FETCH_ASSOC) {
823 					/* zend_hash_quick_update needs length + trailing zero */
824 					/* QQ: Error handling ? */
825 					/*
826 					  zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
827 					  the index is a numeric and convert it to it. This however means constant
828 					  hashing of the column name, which is not needed as it can be precomputed.
829 					*/
830 					Z_ADDREF_P(data);
831 					if (hash_key->is_numeric == FALSE) {
832 #if MYSQLND_UNICODE
833 						zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE,
834 												 hash_key->ustr,
835 												 hash_key->ulen + 1,
836 												 hash_key->key,
837 												 (void *) &data, sizeof(zval *), NULL);
838 #else
839 						zend_hash_quick_update(Z_ARRVAL_P(row),
840 											   field->name,
841 											   field->name_length + 1,
842 											   hash_key->key,
843 											   (void *) &data, sizeof(zval *), NULL);
844 #endif
845 					} else {
846 						zend_hash_index_update(Z_ARRVAL_P(row),
847 											   hash_key->key,
848 											   (void *) &data, sizeof(zval *), NULL);
849 					}
850 				}
851 				if (field->max_length < len) {
852 					field->max_length = len;
853 				}
854 			}
855 		}
856 		*fetched_anything = TRUE;
857 		result->unbuf->row_count++;
858 	} else if (ret == FAIL) {
859 		if (row_packet->error_info.error_no) {
860 			result->conn->error_info = row_packet->error_info;
861 			DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
862 		}
863 		CONN_SET_STATE(result->conn, CONN_READY);
864 		result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
865 	} else if (row_packet->eof) {
866 		/* Mark the connection as usable again */
867 		DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
868 		result->unbuf->eof_reached = TRUE;
869 		result->conn->upsert_status.warning_count = row_packet->warning_count;
870 		result->conn->upsert_status.server_status = row_packet->server_status;
871 		/*
872 		  result->row_packet will be cleaned when
873 		  destroying the result object
874 		*/
875 		if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
876 			CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
877 		} else {
878 			CONN_SET_STATE(result->conn, CONN_READY);
879 		}
880 		result->m.unbuffered_free_last_data(result TSRMLS_CC);
881 	}
882 
883 	DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
884 	DBG_RETURN(PASS);
885 }
886 /* }}} */
887 
888 
889 /* {{{ mysqlnd_res::use_result */
890 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,use_result)891 MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, zend_bool ps TSRMLS_DC)
892 {
893 	DBG_ENTER("mysqlnd_res::use_result");
894 	DBG_INF_FMT("ps=%u", ps);
895 
896 	SET_EMPTY_ERROR(result->conn->error_info);
897 
898 
899 	if (ps == FALSE) {
900 		result->type			= MYSQLND_RES_NORMAL;
901 		result->m.fetch_row		= result->m.fetch_row_normal_unbuffered;
902 		result->m.fetch_lengths	= mysqlnd_fetch_lengths_unbuffered;
903 		result->m.row_decoder	= php_mysqlnd_rowp_read_text_protocol;
904 		result->lengths			= mnd_ecalloc(result->field_count, sizeof(unsigned long));
905 		if (!result->lengths) {
906 			goto oom;
907 		}
908 	} else {
909 		result->type			= MYSQLND_RES_PS_UNBUF;
910 		result->m.fetch_row		= NULL;
911 		/* result->m.fetch_row() will be set in mysqlnd_ps.c */
912 		result->m.fetch_lengths	= NULL; /* makes no sense */
913 		result->m.row_decoder	= php_mysqlnd_rowp_read_binary_protocol;
914 		result->lengths 		= NULL;
915 	}
916 
917 	result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC);
918 	result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED));
919 	if (!result->result_set_memory_pool || !result->unbuf) {
920 		goto oom;
921 	}
922 
923 	/*
924 	  Will be freed in the mysqlnd_internal_free_result_contents() called
925 	  by the resource destructor. mysqlnd_fetch_row_unbuffered() expects
926 	  this to be not NULL.
927 	*/
928 	/* FALSE = non-persistent */
929 	result->row_packet = result->conn->protocol->m.get_row_packet(result->conn->protocol, FALSE TSRMLS_CC);
930 	if (!result->row_packet) {
931 		goto oom;
932 	}
933 	result->row_packet->result_set_memory_pool = result->result_set_memory_pool;
934 	result->row_packet->field_count = result->field_count;
935 	result->row_packet->binary_protocol = ps;
936 	result->row_packet->fields_metadata = result->meta->fields;
937 	result->row_packet->bit_fields_count = result->meta->bit_fields_count;
938 	result->row_packet->bit_fields_total_len = result->meta->bit_fields_total_len;
939 
940 	DBG_RETURN(result);
941 oom:
942 	SET_OOM_ERROR(result->conn->error_info);
943 	DBG_RETURN(NULL);
944 }
945 /* }}} */
946 
947 
948 /* {{{ mysqlnd_fetch_row_buffered_c */
949 static MYSQLND_ROW_C
mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC)950 mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC)
951 {
952 	MYSQLND_ROW_C ret = NULL;
953 	MYSQLND_RES_BUFFERED *set = result->stored_data;
954 
955 	DBG_ENTER("mysqlnd_fetch_row_buffered_c");
956 
957 	/* If we haven't read everything */
958 	if (set->data_cursor &&
959 		(set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
960 	{
961 		zval **current_row = set->data_cursor;
962 		MYSQLND_FIELD *field = result->meta->fields;
963 		struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
964 		unsigned int i;
965 
966 		if (NULL == current_row[0]) {
967 			uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count;
968 			enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num],
969 											current_row,
970 											result->meta->field_count,
971 											result->meta->fields,
972 											FALSE,
973 											result->conn->options.numeric_and_datetime_as_unicode,
974 											result->conn->options.int_and_float_native,
975 											result->conn->stats TSRMLS_CC);
976 			if (rc != PASS) {
977 				DBG_RETURN(ret);
978 			}
979 			set->initialized_rows++;
980 			for (i = 0; i < result->field_count; i++) {
981 				/*
982 				  NULL fields are 0 length, 0 is not more than 0
983 				  String of zero size, definitely can't be the next max_length.
984 				  Thus for NULL and zero-length we are quite efficient.
985 				*/
986 				if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
987 					unsigned long len = Z_STRLEN_P(current_row[i]);
988 					if (field->max_length < len) {
989 						field->max_length = len;
990 					}
991 				}
992 			}
993 		}
994 
995 		set->data_cursor += result->meta->field_count;
996 		MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
997 
998 		ret = mnd_malloc(result->field_count * sizeof(char *));
999 		if (ret) {
1000 			for (i = 0; i < result->field_count; i++, field++, hash_key++) {
1001 				zval *data = current_row[i];
1002 
1003 				if (Z_TYPE_P(data) != IS_NULL) {
1004 					convert_to_string(data);
1005 					ret[i] = Z_STRVAL_P(data);
1006 				} else {
1007 					ret[i] = NULL;
1008 				}
1009 			}
1010 		}
1011 		/* there is no conn handle in this function thus we can't set OOM in error_info */
1012 	} else {
1013 		set->data_cursor = NULL;
1014 		DBG_INF("EOF reached");
1015 	}
1016 	DBG_RETURN(ret);
1017 }
1018 /* }}} */
1019 
1020 
1021 /* {{{ mysqlnd_fetch_row_buffered */
1022 static enum_func_status
mysqlnd_fetch_row_buffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)1023 mysqlnd_fetch_row_buffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
1024 {
1025 	unsigned int i;
1026 	zval *row = (zval *) param;
1027 	MYSQLND_RES_BUFFERED *set = result->stored_data;
1028 	enum_func_status ret = FAIL;
1029 
1030 	DBG_ENTER("mysqlnd_fetch_row_buffered");
1031 	DBG_INF_FMT("flags=%u row=%p", flags, row);
1032 
1033 	/* If we haven't read everything */
1034 	if (set->data_cursor &&
1035 		(set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
1036 	{
1037 		zval **current_row = set->data_cursor;
1038 		MYSQLND_FIELD *field = result->meta->fields;
1039 		struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
1040 
1041 		if (NULL == current_row[0]) {
1042 			uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count;
1043 			enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num],
1044 											current_row,
1045 											result->meta->field_count,
1046 											result->meta->fields,
1047 											result->stored_data->persistent,
1048 											result->conn->options.numeric_and_datetime_as_unicode,
1049 											result->conn->options.int_and_float_native,
1050 											result->conn->stats TSRMLS_CC);
1051 			if (rc != PASS) {
1052 				DBG_RETURN(FAIL);
1053 			}
1054 			set->initialized_rows++;
1055 			for (i = 0; i < result->field_count; i++) {
1056 				/*
1057 				  NULL fields are 0 length, 0 is not more than 0
1058 				  String of zero size, definitely can't be the next max_length.
1059 				  Thus for NULL and zero-length we are quite efficient.
1060 				*/
1061 				if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
1062 					unsigned long len = Z_STRLEN_P(current_row[i]);
1063 					if (field->max_length < len) {
1064 						field->max_length = len;
1065 					}
1066 				}
1067 			}
1068 		}
1069 
1070 		for (i = 0; i < result->field_count; i++, field++, hash_key++) {
1071 			zval *data = current_row[i];
1072 
1073 			if (flags & MYSQLND_FETCH_NUM) {
1074 				Z_ADDREF_P(data);
1075 				zend_hash_next_index_insert(Z_ARRVAL_P(row), &data, sizeof(zval *), NULL);
1076 			}
1077 			if (flags & MYSQLND_FETCH_ASSOC) {
1078 				/* zend_hash_quick_update needs length + trailing zero */
1079 				/* QQ: Error handling ? */
1080 				/*
1081 				  zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1082 				  the index is a numeric and convert it to it. This however means constant
1083 				  hashing of the column name, which is not needed as it can be precomputed.
1084 				*/
1085 				Z_ADDREF_P(data);
1086 				if (hash_key->is_numeric == FALSE) {
1087 #if MYSQLND_UNICODE
1088 					zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE,
1089 											 hash_key->ustr,
1090 											 hash_key->ulen + 1,
1091 											 hash_key->key,
1092 											 (void *) &data, sizeof(zval *), NULL);
1093 #else
1094 					zend_hash_quick_update(Z_ARRVAL_P(row),
1095 										   field->name,
1096 										   field->name_length + 1,
1097 										   hash_key->key,
1098 										   (void *) &data, sizeof(zval *), NULL);
1099 #endif
1100 				} else {
1101 					zend_hash_index_update(Z_ARRVAL_P(row),
1102 										   hash_key->key,
1103 										   (void *) &data, sizeof(zval *), NULL);
1104 				}
1105 			}
1106 		}
1107 		set->data_cursor += result->meta->field_count;
1108 		*fetched_anything = TRUE;
1109 		MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
1110 		ret = PASS;
1111 	} else {
1112 		set->data_cursor = NULL;
1113 		*fetched_anything = FALSE;
1114 		ret = PASS;
1115 		DBG_INF("EOF reached");
1116 	}
1117 	DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
1118 	DBG_RETURN(ret);
1119 }
1120 /* }}} */
1121 
1122 
1123 #define STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY 2
1124 
1125 /* {{{ mysqlnd_res::store_result_fetch_data */
1126 enum_func_status
MYSQLND_METHOD(mysqlnd_res,store_result_fetch_data)1127 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND * const conn, MYSQLND_RES * result,
1128 													MYSQLND_RES_METADATA *meta,
1129 													zend_bool binary_protocol,
1130 													zend_bool to_cache TSRMLS_DC)
1131 {
1132 	enum_func_status ret;
1133 	MYSQLND_PACKET_ROW *row_packet = NULL;
1134 	unsigned int next_extend = STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY, free_rows = 1;
1135 	MYSQLND_RES_BUFFERED *set;
1136 
1137 	DBG_ENTER("mysqlnd_res::store_result_fetch_data");
1138 	DBG_INF_FMT("conn=%llu binary_proto=%u to_cache=%u",
1139 				conn->thread_id, binary_protocol, to_cache);
1140 
1141 	result->stored_data	= set = mnd_pecalloc(1, sizeof(MYSQLND_RES_BUFFERED), to_cache);
1142 	if (!set) {
1143 		SET_OOM_ERROR(conn->error_info);
1144 		ret = FAIL;
1145 		goto end;
1146 	}
1147 	if (free_rows) {
1148 		set->row_buffers = mnd_pemalloc((size_t)(free_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)), to_cache);
1149 		if (!set->row_buffers) {
1150 			SET_OOM_ERROR(conn->error_info);
1151 			ret = FAIL;
1152 			goto end;
1153 		}
1154 	}
1155 	set->persistent	= to_cache;
1156 	set->references	= 1;
1157 
1158 	result->m.row_decoder = binary_protocol? php_mysqlnd_rowp_read_binary_protocol:
1159 											 php_mysqlnd_rowp_read_text_protocol;
1160 
1161 	/* non-persistent */
1162 	row_packet = conn->protocol->m.get_row_packet(conn->protocol, FALSE TSRMLS_CC);
1163 	if (!row_packet) {
1164 		SET_OOM_ERROR(conn->error_info);
1165 		ret = FAIL;
1166 		goto end;
1167 	}
1168 	row_packet->result_set_memory_pool = result->result_set_memory_pool;
1169 	row_packet->field_count = meta->field_count;
1170 	row_packet->binary_protocol = binary_protocol;
1171 	row_packet->fields_metadata = meta->fields;
1172 	row_packet->bit_fields_count	= meta->bit_fields_count;
1173 	row_packet->bit_fields_total_len = meta->bit_fields_total_len;
1174 
1175 	row_packet->skip_extraction = TRUE; /* let php_mysqlnd_rowp_read() not allocate row_packet->fields, we will do it */
1176 
1177 	while (FAIL != (ret = PACKET_READ(row_packet, conn)) && !row_packet->eof) {
1178 		if (!free_rows) {
1179 			uint64_t total_allocated_rows = free_rows = next_extend = next_extend * 11 / 10; /* extend with 10% */
1180 			MYSQLND_MEMORY_POOL_CHUNK ** new_row_buffers;
1181 			total_allocated_rows += set->row_count;
1182 
1183 			/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1184 			if (total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) {
1185 				SET_OOM_ERROR(conn->error_info);
1186 				ret = FAIL;
1187 				goto end;
1188 			}
1189 			new_row_buffers = mnd_perealloc(set->row_buffers,
1190 											(size_t)(total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)),
1191 											set->persistent);
1192 			if (!new_row_buffers) {
1193 				SET_OOM_ERROR(conn->error_info);
1194 				ret = FAIL;
1195 				goto end;
1196 			}
1197 			set->row_buffers = new_row_buffers;
1198 		}
1199 		free_rows--;
1200 		set->row_buffers[set->row_count] = row_packet->row_buffer;
1201 
1202 		set->row_count++;
1203 
1204 		/* So row_packet's destructor function won't efree() it */
1205 		row_packet->fields = NULL;
1206 		row_packet->row_buffer = NULL;
1207 
1208 		/*
1209 		  No need to FREE_ALLOCA as we can reuse the
1210 		  'lengths' and 'fields' arrays. For lengths its absolutely safe.
1211 		  'fields' is reused because the ownership of the strings has been
1212 		  transfered above.
1213 		*/
1214 	}
1215 	/* Overflow ? */
1216 	if (set->row_count) {
1217 		/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1218 		if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) {
1219 			SET_OOM_ERROR(conn->error_info);
1220 			ret = FAIL;
1221 			goto end;
1222 		}
1223 		/* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */
1224 		set->data = mnd_pemalloc((size_t)(set->row_count * meta->field_count * sizeof(zval *)), to_cache);
1225 		if (!set->data) {
1226 			SET_OOM_ERROR(conn->error_info);
1227 			ret = FAIL;
1228 			goto end;
1229 		}
1230 		memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval *)));
1231 	}
1232 
1233 	MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats,
1234 									   binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
1235 														STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
1236 									   set->row_count);
1237 
1238 	/* Finally clean */
1239 	if (row_packet->eof) {
1240 		conn->upsert_status.warning_count = row_packet->warning_count;
1241 		conn->upsert_status.server_status = row_packet->server_status;
1242 	}
1243 	/* save some memory */
1244 	if (free_rows) {
1245 		/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1246 		if (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) {
1247 			SET_OOM_ERROR(conn->error_info);
1248 			ret = FAIL;
1249 			goto end;
1250 		}
1251 		set->row_buffers = mnd_perealloc(set->row_buffers,
1252 										 (size_t) (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)),
1253 										 set->persistent);
1254 	}
1255 
1256 	if (conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
1257 		CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
1258 	} else {
1259 		CONN_SET_STATE(conn, CONN_READY);
1260 	}
1261 
1262 	if (ret == FAIL) {
1263 		set->error_info = row_packet->error_info;
1264 	} else {
1265 		/* Position at the first row */
1266 		set->data_cursor = set->data;
1267 
1268 		/* libmysql's documentation says it should be so for SELECT statements */
1269 		conn->upsert_status.affected_rows = set->row_count;
1270 	}
1271 	DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u",
1272 				ret == PASS? "PASS":"FAIL", (uint) set->row_count, conn->upsert_status.warning_count, conn->upsert_status.server_status);
1273 end:
1274 	PACKET_FREE(row_packet);
1275 
1276 	DBG_RETURN(ret);
1277 }
1278 /* }}} */
1279 
1280 
1281 /* {{{ mysqlnd_res::store_result */
1282 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,store_result)1283 MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
1284 										  MYSQLND * const conn,
1285 										  zend_bool ps_protocol TSRMLS_DC)
1286 {
1287 	enum_func_status ret;
1288 	zend_bool to_cache = FALSE;
1289 
1290 	DBG_ENTER("mysqlnd_res::store_result");
1291 	DBG_INF_FMT("conn=%u ps_protocol=%u", conn->thread_id, ps_protocol);
1292 
1293 	/* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
1294 	result->conn 			= conn->m->get_reference(conn TSRMLS_CC);
1295 	result->type			= MYSQLND_RES_NORMAL;
1296 	result->m.fetch_row		= result->m.fetch_row_normal_buffered;
1297 	result->m.fetch_lengths	= mysqlnd_fetch_lengths_buffered;
1298 
1299 	result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC);
1300 	result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long));
1301 	if (!result->result_set_memory_pool || !result->lengths) {
1302 		SET_OOM_ERROR(conn->error_info);
1303 		DBG_RETURN(NULL);
1304 	}
1305 
1306 	CONN_SET_STATE(conn, CONN_FETCHING_DATA);
1307 
1308 	ret = result->m.store_result_fetch_data(conn, result, result->meta, ps_protocol, to_cache TSRMLS_CC);
1309 	if (FAIL == ret) {
1310 		if (result->stored_data) {
1311 			conn->error_info = result->stored_data->error_info;
1312 		} else {
1313 			SET_OOM_ERROR(conn->error_info);
1314 		}
1315 		DBG_RETURN(NULL);
1316 	}
1317 	/* libmysql's documentation says it should be so for SELECT statements */
1318 	conn->upsert_status.affected_rows = result->stored_data->row_count;
1319 
1320 	DBG_RETURN(result);
1321 }
1322 /* }}} */
1323 
1324 
1325 /* {{{ mysqlnd_res::skip_result */
1326 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,skip_result)1327 MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result TSRMLS_DC)
1328 {
1329 	zend_bool fetched_anything;
1330 
1331 	DBG_ENTER("mysqlnd_res::skip_result");
1332 	/*
1333 	  Unbuffered sets
1334 	  A PS could be prepared - there is metadata and thus a stmt->result but the
1335 	  fetch_row function isn't actually set (NULL), thus we have to skip these.
1336 	*/
1337 	if (!result->stored_data && result->unbuf &&
1338 		!result->unbuf->eof_reached && result->m.fetch_row)
1339 	{
1340 		DBG_INF("skipping result");
1341 		/* We have to fetch all data to clean the line */
1342 		MYSQLND_INC_CONN_STATISTIC(result->conn->stats,
1343 									result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS:
1344 																		STAT_FLUSHED_PS_SETS);
1345 
1346 		while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything TSRMLS_CC)) && fetched_anything == TRUE) {
1347 			/* do nothing */;
1348 		}
1349 	}
1350 	DBG_RETURN(PASS);
1351 }
1352 /* }}} */
1353 
1354 
1355 /* {{{ mysqlnd_res::free_result */
1356 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,free_result)1357 MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC)
1358 {
1359 	DBG_ENTER("mysqlnd_res::free_result");
1360 	DBG_INF_FMT("implicit=%u", implicit);
1361 
1362 	result->m.skip_result(result TSRMLS_CC);
1363 	MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL,
1364 							   implicit == TRUE?	STAT_FREE_RESULT_IMPLICIT:
1365 							   						STAT_FREE_RESULT_EXPLICIT);
1366 
1367 	result->m.free_result_internal(result TSRMLS_CC);
1368 	DBG_RETURN(PASS);
1369 }
1370 /* }}} */
1371 
1372 
1373 /* {{{ mysqlnd_res::data_seek */
1374 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,data_seek)1375 MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * result, uint64_t row TSRMLS_DC)
1376 {
1377 	DBG_ENTER("mysqlnd_res::data_seek");
1378 	DBG_INF_FMT("row=%lu", row);
1379 
1380 	if (!result->stored_data) {
1381 		return FAIL;
1382 	}
1383 
1384 	/* libmysql just moves to the end, it does traversing of a linked list */
1385 	if (row >= result->stored_data->row_count) {
1386 		result->stored_data->data_cursor = NULL;
1387 	} else {
1388 		result->stored_data->data_cursor = result->stored_data->data + row * result->meta->field_count;
1389 	}
1390 
1391 	DBG_RETURN(PASS);
1392 }
1393 /* }}} */
1394 
1395 
1396 /* {{{ mysqlnd_res::num_rows */
1397 static uint64_t
MYSQLND_METHOD(mysqlnd_res,num_rows)1398 MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result TSRMLS_DC)
1399 {
1400 	/* Be compatible with libmysql. We count row_count, but will return 0 */
1401 	return result->stored_data? result->stored_data->row_count:(result->unbuf && result->unbuf->eof_reached? result->unbuf->row_count:0);
1402 }
1403 /* }}} */
1404 
1405 
1406 /* {{{ mysqlnd_res::num_fields */
1407 static unsigned int
MYSQLND_METHOD(mysqlnd_res,num_fields)1408 MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result TSRMLS_DC)
1409 {
1410 	return result->field_count;
1411 }
1412 /* }}} */
1413 
1414 
1415 /* {{{ mysqlnd_res::fetch_field */
1416 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field)1417 MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result TSRMLS_DC)
1418 {
1419 	DBG_ENTER("mysqlnd_res::fetch_field");
1420 	do {
1421 		if (result->meta) {
1422 			/*
1423 			  We optimize the result set, so we don't convert all the data from raw buffer format to
1424 			  zval arrays during store. In the case someone doesn't read all the lines this will
1425 			  save time. However, when a metadata call is done, we need to calculate max_length.
1426 			  We don't have control whether max_length will be used, unfortunately. Otherwise we
1427 			  could have been able to skip that step.
1428 			  Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1429 			  then we can have max_length as dynamic property, which will be calculated during runtime and
1430 			  not during mysqli_fetch_field() time.
1431 			*/
1432 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1433 				DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1434 				/* we have to initialize the rest to get the updated max length */
1435 				if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
1436 					break;
1437 				}
1438 			}
1439 			DBG_RETURN(result->meta->m->fetch_field(result->meta TSRMLS_CC));
1440 		}
1441 	} while (0);
1442 	DBG_RETURN(NULL);
1443 }
1444 /* }}} */
1445 
1446 
1447 /* {{{ mysqlnd_res::fetch_field_direct */
1448 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field_direct)1449 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC)
1450 {
1451 	DBG_ENTER("mysqlnd_res::fetch_field_direct");
1452 	do {
1453 		if (result->meta) {
1454 			/*
1455 			  We optimize the result set, so we don't convert all the data from raw buffer format to
1456 			  zval arrays during store. In the case someone doesn't read all the lines this will
1457 			  save time. However, when a metadata call is done, we need to calculate max_length.
1458 			  We don't have control whether max_length will be used, unfortunately. Otherwise we
1459 			  could have been able to skip that step.
1460 			  Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1461 			  then we can have max_length as dynamic property, which will be calculated during runtime and
1462 			  not during mysqli_fetch_field_direct() time.
1463 			*/
1464 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1465 				DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1466 				/* we have to initialized the rest to get the updated max length */
1467 				if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
1468 					break;
1469 				}
1470 			}
1471 			DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr TSRMLS_CC));
1472 		}
1473 	} while (0);
1474 
1475 	DBG_RETURN(NULL);
1476 }
1477 /* }}} */
1478 
1479 
1480 /* {{{ mysqlnd_res::fetch_field */
1481 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_fields)1482 MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result TSRMLS_DC)
1483 {
1484 	DBG_ENTER("mysqlnd_res::fetch_fields");
1485 	do {
1486 		if (result->meta) {
1487 			if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1488 				/* we have to initialize the rest to get the updated max length */
1489 				if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
1490 					break;
1491 				}
1492 			}
1493 			DBG_RETURN(result->meta->m->fetch_fields(result->meta TSRMLS_CC));
1494 		}
1495 	} while (0);
1496 	DBG_RETURN(NULL);
1497 }
1498 /* }}} */
1499 
1500 
1501 
1502 /* {{{ mysqlnd_res::field_seek */
1503 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_seek)1504 MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC)
1505 {
1506 	MYSQLND_FIELD_OFFSET return_value = 0;
1507 	if (result->meta) {
1508 		return_value = result->meta->current_field;
1509 		result->meta->current_field = field_offset;
1510 	}
1511 	return return_value;
1512 }
1513 /* }}} */
1514 
1515 
1516 /* {{{ mysqlnd_res::field_tell */
1517 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_tell)1518 MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result TSRMLS_DC)
1519 {
1520 	return result->meta? result->meta->m->field_tell(result->meta TSRMLS_CC) : 0;
1521 }
1522 /* }}} */
1523 
1524 
1525 /* for php_addslashes */
1526 #include "ext/standard/php_string.h"
1527 
1528 /* {{{ mysqlnd_res::fetch_into */
1529 static void
MYSQLND_METHOD(mysqlnd_res,fetch_into)1530 MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, unsigned int flags,
1531 										zval *return_value,
1532 										enum_mysqlnd_extension extension TSRMLS_DC ZEND_FILE_LINE_DC)
1533 {
1534 	zend_bool fetched_anything;
1535 
1536 	DBG_ENTER("mysqlnd_res::fetch_into");
1537 	DBG_INF_FMT("flags=%u mysqlnd_extension=%u", flags, extension);
1538 
1539 	if (!result->m.fetch_row) {
1540 		RETVAL_NULL();
1541 		DBG_VOID_RETURN;
1542 	}
1543 	/*
1544 	  Hint Zend how many elements we will have in the hash. Thus it won't
1545 	  extend and rehash the hash constantly.
1546 	*/
1547 	mysqlnd_array_init(return_value, mysqlnd_num_fields(result) * 2);
1548 	if (FAIL == result->m.fetch_row(result, (void *)return_value, flags, &fetched_anything TSRMLS_CC)) {
1549 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading a row");
1550 		zval_dtor(return_value);
1551 		RETVAL_FALSE;
1552 	} else if (fetched_anything == FALSE) {
1553 		zval_dtor(return_value);
1554 		switch (extension) {
1555 			case MYSQLND_MYSQLI:
1556 				RETVAL_NULL();
1557 				break;
1558 			case MYSQLND_MYSQL:
1559 				RETVAL_FALSE;
1560 				break;
1561 			default:exit(0);
1562 		}
1563 	}
1564 	/*
1565 	  return_value is IS_NULL for no more data and an array for data. Thus it's ok
1566 	  to return here.
1567 	*/
1568 	DBG_VOID_RETURN;
1569 }
1570 /* }}} */
1571 
1572 
1573 /* {{{ mysqlnd_res::fetch_row_c */
1574 static MYSQLND_ROW_C
MYSQLND_METHOD(mysqlnd_res,fetch_row_c)1575 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result TSRMLS_DC)
1576 {
1577 	MYSQLND_ROW_C ret = NULL;
1578 	DBG_ENTER("mysqlnd_res::fetch_row_c");
1579 
1580 	if (result->m.fetch_row) {
1581 		if (result->m.fetch_row == result->m.fetch_row_normal_buffered) {
1582 			DBG_RETURN(mysqlnd_fetch_row_buffered_c(result TSRMLS_CC));
1583 		} else if (result->m.fetch_row == result->m.fetch_row_normal_unbuffered) {
1584 			DBG_RETURN(mysqlnd_fetch_row_unbuffered_c(result TSRMLS_CC));
1585 		} else {
1586 			php_error_docref(NULL TSRMLS_CC, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers");
1587 		}
1588 	}
1589 	DBG_RETURN(ret);
1590 }
1591 /* }}} */
1592 
1593 
1594 /* {{{ mysqlnd_res::fetch_all */
1595 static void
MYSQLND_METHOD(mysqlnd_res,fetch_all)1596 MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC)
1597 {
1598 	zval  *row;
1599 	ulong i = 0;
1600 	MYSQLND_RES_BUFFERED *set = result->stored_data;
1601 
1602 	DBG_ENTER("mysqlnd_res::fetch_all");
1603 	DBG_INF_FMT("flags=%u", flags);
1604 
1605 	if ((!result->unbuf && !set)) {
1606 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "fetch_all can be used only with buffered sets");
1607 		if (result->conn) {
1608 			SET_CLIENT_ERROR(result->conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "fetch_all can be used only with buffered sets");
1609 		}
1610 		RETVAL_NULL();
1611 		DBG_VOID_RETURN;
1612 	}
1613 
1614 	/* 4 is a magic value. The cast is safe, if larger then the array will be later extended - no big deal :) */
1615 	mysqlnd_array_init(return_value, set? (unsigned int) set->row_count : 4);
1616 
1617 	do {
1618 		MAKE_STD_ZVAL(row);
1619 		mysqlnd_fetch_into(result, flags, row, MYSQLND_MYSQLI);
1620 		if (Z_TYPE_P(row) != IS_ARRAY) {
1621 			zval_ptr_dtor(&row);
1622 			break;
1623 		}
1624 		add_index_zval(return_value, i++, row);
1625 	} while (1);
1626 
1627 	DBG_VOID_RETURN;
1628 }
1629 /* }}} */
1630 
1631 
1632 /* {{{ mysqlnd_res::fetch_field_data */
1633 static void
MYSQLND_METHOD(mysqlnd_res,fetch_field_data)1634 MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, unsigned int offset, zval *return_value TSRMLS_DC)
1635 {
1636 	zval row;
1637 	zval **entry;
1638 	unsigned int i = 0;
1639 
1640 	DBG_ENTER("mysqlnd_res::fetch_field_data");
1641 	DBG_INF_FMT("offset=%u", offset);
1642 
1643 	if (!result->m.fetch_row) {
1644 		RETVAL_NULL();
1645 		DBG_VOID_RETURN;
1646 	}
1647 	/*
1648 	  Hint Zend how many elements we will have in the hash. Thus it won't
1649 	  extend and rehash the hash constantly.
1650 	*/
1651 	INIT_PZVAL(&row);
1652 	mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, &row, MYSQLND_MYSQL);
1653 	if (Z_TYPE(row) != IS_ARRAY) {
1654 		zval_dtor(&row);
1655 		RETVAL_NULL();
1656 		DBG_VOID_RETURN;
1657 	}
1658 	zend_hash_internal_pointer_reset(Z_ARRVAL(row));
1659 	while (i++ < offset) {
1660 		zend_hash_move_forward(Z_ARRVAL(row));
1661 		zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry);
1662 	}
1663 
1664 	zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry);
1665 
1666 	*return_value = **entry;
1667 	zval_copy_ctor(return_value);
1668 	Z_SET_REFCOUNT_P(return_value, 1);
1669 	zval_dtor(&row);
1670 
1671 	DBG_VOID_RETURN;
1672 }
1673 /* }}} */
1674 
1675 
1676 static
1677 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1678 	NULL, /* fetch_row */
1679 	mysqlnd_fetch_row_buffered,
1680 	mysqlnd_fetch_row_unbuffered,
1681 	MYSQLND_METHOD(mysqlnd_res, use_result),
1682 	MYSQLND_METHOD(mysqlnd_res, store_result),
1683 	MYSQLND_METHOD(mysqlnd_res, fetch_into),
1684 	MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1685 	MYSQLND_METHOD(mysqlnd_res, fetch_all),
1686 	MYSQLND_METHOD(mysqlnd_res, fetch_field_data),
1687 	MYSQLND_METHOD(mysqlnd_res, num_rows),
1688 	MYSQLND_METHOD(mysqlnd_res, num_fields),
1689 	MYSQLND_METHOD(mysqlnd_res, skip_result),
1690 	MYSQLND_METHOD(mysqlnd_res, data_seek),
1691 	MYSQLND_METHOD(mysqlnd_res, field_seek),
1692 	MYSQLND_METHOD(mysqlnd_res, field_tell),
1693 	MYSQLND_METHOD(mysqlnd_res, fetch_field),
1694 	MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1695 	MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1696 	MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1697 	NULL, /* fetch_lengths */
1698 	MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1699 	MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest),
1700 	MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1701 	MYSQLND_METHOD(mysqlnd_res, free_result),
1702 
1703 	mysqlnd_internal_free_result, /* free_result_internal */
1704 	mysqlnd_internal_free_result_contents, /* free_result_contents */
1705 	MYSQLND_METHOD(mysqlnd_res, free_buffered_data),
1706 	MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data),
1707 
1708 	NULL /* row_decoder */,
1709 	mysqlnd_result_meta_init
1710 MYSQLND_CLASS_METHODS_END;
1711 
1712 
1713 /* {{{ mysqlnd_result_init */
1714 PHPAPI MYSQLND_RES *
mysqlnd_result_init(unsigned int field_count,zend_bool persistent TSRMLS_DC)1715 mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC)
1716 {
1717 	size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1718 	MYSQLND_RES *ret = mnd_pecalloc(1, alloc_size, persistent);
1719 
1720 	DBG_ENTER("mysqlnd_result_init");
1721 	DBG_INF_FMT("field_count=%u", field_count);
1722 
1723 	if (!ret) {
1724 		DBG_RETURN(NULL);
1725 	}
1726 
1727 	ret->persistent		= persistent;
1728 	ret->field_count	= field_count;
1729 	ret->m = mysqlnd_mysqlnd_res_methods;
1730 
1731 	DBG_RETURN(ret);
1732 }
1733 /* }}} */
1734 
1735 
1736 /* {{{ _mysqlnd_plugin_get_plugin_result_data */
_mysqlnd_plugin_get_plugin_result_data(const MYSQLND_RES * result,unsigned int plugin_id TSRMLS_DC)1737 PHPAPI void ** _mysqlnd_plugin_get_plugin_result_data(const MYSQLND_RES * result, unsigned int plugin_id TSRMLS_DC)
1738 {
1739 	DBG_ENTER("_mysqlnd_plugin_get_plugin_result_data");
1740 	DBG_INF_FMT("plugin_id=%u", plugin_id);
1741 	if (!result || plugin_id >= mysqlnd_plugin_count()) {
1742 		return NULL;
1743 	}
1744 	DBG_RETURN((void *)((char *)result + sizeof(MYSQLND_RES) + plugin_id * sizeof(void *)));
1745 }
1746 /* }}} */
1747 
1748 
1749 /* {{{ mysqlnd_result_get_methods */
1750 PHPAPI struct st_mysqlnd_res_methods *
mysqlnd_result_get_methods()1751 mysqlnd_result_get_methods()
1752 {
1753 	return &mysqlnd_mysqlnd_res_methods;
1754 }
1755 /* }}} */
1756 
1757 /*
1758  * Local variables:
1759  * tab-width: 4
1760  * c-basic-offset: 4
1761  * End:
1762  * vim600: noet sw=4 ts=4 fdm=marker
1763  * vim<600: noet sw=4 ts=4
1764  */
1765