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