xref: /php-src/ext/mysqlnd/mysqlnd_result.c (revision 3b93f131)
1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Authors: Andrey Hristov <andrey@php.net>                             |
14   |          Ulf Wendel <uw@php.net>                                     |
15   +----------------------------------------------------------------------+
16 */
17 
18 #include "php.h"
19 #include "mysqlnd.h"
20 #include "mysqlnd_wireprotocol.h"
21 #include "mysqlnd_block_alloc.h"
22 #include "mysqlnd_connection.h"
23 #include "mysqlnd_priv.h"
24 #include "mysqlnd_result.h"
25 #include "mysqlnd_result_meta.h"
26 #include "mysqlnd_statistics.h"
27 #include "mysqlnd_debug.h"
28 #include "mysqlnd_ext_plugin.h"
29 
30 /* {{{ mysqlnd_result_unbuffered::free_result */
31 static void
MYSQLND_METHOD(mysqlnd_result_unbuffered,free_result)32 MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)(MYSQLND_RES_UNBUFFERED * const result, MYSQLND_STATS * const global_stats)
33 {
34 	DBG_ENTER("mysqlnd_result_unbuffered, free_result");
35 
36 	/* must be free before because references the memory pool */
37 	if (result->row_packet) {
38 		PACKET_FREE(result->row_packet);
39 		mnd_efree(result->row_packet);
40 		result->row_packet = NULL;
41 	}
42 
43 	DBG_VOID_RETURN;
44 }
45 /* }}} */
46 
mysqlnd_result_free_prev_data(MYSQLND_RES * result)47 static void mysqlnd_result_free_prev_data(MYSQLND_RES *result)
48 {
49 	if (result->free_row_data) {
50 		for (unsigned i = 0; i < result->field_count; ++i) {
51 			zval_ptr_dtor_nogc(&result->row_data[i]);
52 		}
53 		result->free_row_data = 0;
54 	}
55 }
56 
57 /* {{{ mysqlnd_result_buffered::free_result */
58 static void
MYSQLND_METHOD(mysqlnd_result_buffered,free_result)59 MYSQLND_METHOD(mysqlnd_result_buffered, free_result)(MYSQLND_RES_BUFFERED * const set)
60 {
61 
62 	DBG_ENTER("mysqlnd_result_buffered::free_result");
63 	DBG_INF_FMT("Freeing %" PRIu64 " row(s)", set->row_count);
64 
65 	mysqlnd_error_info_free_contents(&set->error_info);
66 
67 	if (set->row_buffers) {
68 		mnd_efree(set->row_buffers);
69 		set->row_buffers = NULL;
70 	}
71 
72 	DBG_VOID_RETURN;
73 }
74 /* }}} */
75 
76 
77 /* {{{ mysqlnd_res::free_result_buffers */
78 static void
MYSQLND_METHOD(mysqlnd_res,free_result_buffers)79 MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES * result)
80 {
81 	DBG_ENTER("mysqlnd_res::free_result_buffers");
82 	DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown"));
83 
84 	mysqlnd_result_free_prev_data(result);
85 
86 	if (result->meta) {
87 		ZEND_ASSERT(zend_arena_contains(result->memory_pool->arena, result->meta));
88 		result->meta->m->free_metadata(result->meta);
89 		result->meta = NULL;
90 	}
91 
92 	if (result->unbuf) {
93 		result->unbuf->m.free_result(result->unbuf, result->conn? result->conn->stats : NULL);
94 		result->unbuf = NULL;
95 	} else if (result->stored_data) {
96 		result->stored_data->m.free_result(result->stored_data);
97 		result->stored_data = NULL;
98 	}
99 
100 	mysqlnd_mempool_restore_state(result->memory_pool);
101 	mysqlnd_mempool_save_state(result->memory_pool);
102 
103 	DBG_VOID_RETURN;
104 }
105 /* }}} */
106 
107 
108 /* {{{ mysqlnd_res::free_result_contents_internal */
109 static
MYSQLND_METHOD(mysqlnd_res,free_result_contents_internal)110 void MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal)(MYSQLND_RES * result)
111 {
112 	DBG_ENTER("mysqlnd_res::free_result_contents_internal");
113 
114 	result->m.free_result_buffers(result);
115 
116 	if (result->conn) {
117 		result->conn->m->free_reference(result->conn);
118 		result->conn = NULL;
119 	}
120 
121 	mysqlnd_mempool_destroy(result->memory_pool);
122 
123 	DBG_VOID_RETURN;
124 }
125 /* }}} */
126 
127 
128 /* {{{ mysqlnd_res::read_result_metadata */
129 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,read_result_metadata)130 MYSQLND_METHOD(mysqlnd_res, read_result_metadata)(MYSQLND_RES * result, MYSQLND_CONN_DATA * conn)
131 {
132 	DBG_ENTER("mysqlnd_res::read_result_metadata");
133 
134 	/*
135 	  Make it safe to call it repeatedly for PS -
136 	  better free and allocate a new because the number of field might change
137 	  (select *) with altered table. Also for statements which skip the PS
138 	  infrastructure!
139 	*/
140 	if (result->meta) {
141 		result->meta->m->free_metadata(result->meta);
142 		result->meta = NULL;
143 	}
144 
145 	result->meta = result->m.result_meta_init(result, result->field_count);
146 
147 	/* 1. Read all fields metadata */
148 
149 	/* It's safe to reread without freeing */
150 	if (FAIL == result->meta->m->read_metadata(result->meta, conn, result)) {
151 		result->meta->m->free_metadata(result->meta);
152 		result->meta = NULL;
153 		DBG_RETURN(FAIL);
154 	}
155 
156 	/*
157 	  2. Follows an EOF packet, which the client of mysqlnd_read_result_metadata()
158 	     should consume.
159 	  3. If there is a result set, it follows. The last packet will have 'eof' set
160 	     If PS, then no result set follows.
161 	*/
162 
163 	DBG_RETURN(PASS);
164 }
165 /* }}} */
166 
167 
168 /* {{{ mysqlnd_query_read_result_set_header */
169 enum_func_status
mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn,MYSQLND_STMT * s)170 mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s)
171 {
172 	enum_func_status ret;
173 	MYSQLND_STMT_DATA * stmt = s ? s->data : NULL;
174 	MYSQLND_PACKET_RSET_HEADER rset_header;
175 	MYSQLND_PACKET_EOF fields_eof;
176 
177 	DBG_ENTER("mysqlnd_query_read_result_set_header");
178 	DBG_INF_FMT("stmt=" ZEND_ULONG_FMT, stmt? stmt->stmt_id:0);
179 
180 	ret = FAIL;
181 	do {
182 		conn->payload_decoder_factory->m.init_rset_header_packet(&rset_header);
183 		UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
184 
185 		if (FAIL == (ret = PACKET_READ(conn, &rset_header))) {
186 			if (conn->error_info->error_no != CR_SERVER_GONE_ERROR) {
187 				php_error_docref(NULL, E_WARNING, "Error reading result set's header");
188 			}
189 			break;
190 		}
191 
192 		if (rset_header.error_info.error_no) {
193 			/*
194 			  Cover a protocol design error: error packet does not
195 			  contain the server status. Therefore, the client has no way
196 			  to find out whether there are more result sets of
197 			  a multiple-result-set statement pending. Luckily, in 5.0 an
198 			  error always aborts execution of a statement, wherever it is
199 			  a multi-statement or a stored procedure, so it should be
200 			  safe to unconditionally turn off the flag here.
201 			*/
202 			UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & ~SERVER_MORE_RESULTS_EXISTS);
203 			/*
204 			  This will copy the error code and the messages, as they
205 			  are buffers in the struct
206 			*/
207 			COPY_CLIENT_ERROR(conn->error_info, rset_header.error_info);
208 			ret = FAIL;
209 			DBG_ERR_FMT("error=%s", rset_header.error_info.error);
210 			/* Return back from CONN_QUERY_SENT */
211 			SET_CONNECTION_STATE(&conn->state, CONN_READY);
212 			break;
213 		}
214 		conn->error_info->error_no = 0;
215 
216 		switch (rset_header.field_count) {
217 			case MYSQLND_NULL_LENGTH: {	/* LOAD DATA LOCAL INFILE */
218 				bool is_warning;
219 				DBG_INF("LOAD DATA");
220 				conn->last_query_type = QUERY_LOAD_LOCAL;
221 				conn->field_count = 0; /* overwrite previous value, or the last value could be used and lead to bug#53503 */
222 				SET_CONNECTION_STATE(&conn->state, CONN_SENDING_LOAD_DATA);
223 				ret = mysqlnd_handle_local_infile(conn, rset_header.info_or_local_file.s, &is_warning);
224 				SET_CONNECTION_STATE(&conn->state,  (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT);
225 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
226 				break;
227 			}
228 			case 0:				/* UPSERT */
229 				DBG_INF("UPSERT");
230 				conn->last_query_type = QUERY_UPSERT;
231 				conn->field_count = rset_header.field_count;
232 				UPSERT_STATUS_RESET(conn->upsert_status);
233 				UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, rset_header.warning_count);
234 				UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, rset_header.server_status);
235 				UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, rset_header.affected_rows);
236 				UPSERT_STATUS_SET_LAST_INSERT_ID(conn->upsert_status, rset_header.last_insert_id);
237 				mysqlnd_set_string(&conn->last_message, rset_header.info_or_local_file.s, rset_header.info_or_local_file.l);
238 				/* Result set can follow UPSERT statement, check server_status */
239 				if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
240 					SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
241 				} else {
242 					SET_CONNECTION_STATE(&conn->state, CONN_READY);
243 				}
244 				ret = PASS;
245 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
246 				break;
247 			default: do {			/* Result set */
248 				MYSQLND_RES * result;
249 				enum_mysqlnd_collected_stats statistic = STAT_LAST;
250 
251 				DBG_INF("Result set pending");
252 				mysqlnd_set_string(&conn->last_message, NULL, 0);
253 
254 				MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_RSET_QUERY);
255 				UPSERT_STATUS_RESET(conn->upsert_status);
256 				/* restore after zeroing */
257 				UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
258 
259 				conn->last_query_type = QUERY_SELECT;
260 				SET_CONNECTION_STATE(&conn->state, CONN_FETCHING_DATA);
261 				/* PS has already allocated it */
262 				conn->field_count = rset_header.field_count;
263 				if (!stmt) {
264 					result = conn->current_result = conn->m->result_init(rset_header.field_count);
265 				} else {
266 					if (!stmt->result) {
267 						DBG_INF("This is 'SHOW'/'EXPLAIN'-like query.");
268 						/*
269 						  This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
270 						  prepared statements can't send result set metadata for these queries
271 						  on prepare stage. Read it now.
272 						*/
273 						result = stmt->result = conn->m->result_init(rset_header.field_count);
274 					} else {
275 						/*
276 						  Update result set metadata if it for some reason changed between
277 						  prepare and execute, i.e.:
278 						  - in case of 'SELECT ?' we don't know column type unless data was
279 							supplied to mysql_stmt_execute, so updated column type is sent
280 							now.
281 						  - if data dictionary changed between prepare and execute, for
282 							example a table used in the query was altered.
283 						  Note, that now (4.1.3) we always send metadata in reply to
284 						  COM_STMT_EXECUTE (even if it is not necessary), so either this or
285 						  previous branch always works.
286 						*/
287 						if (rset_header.field_count != stmt->result->field_count) {
288 							stmt->result->m.free_result(stmt->result, TRUE);
289 							stmt->result = conn->m->result_init(rset_header.field_count);
290 						}
291 						result = stmt->result;
292 					}
293 				}
294 				if (!result) {
295 					SET_OOM_ERROR(conn->error_info);
296 					ret = FAIL;
297 					break;
298 				}
299 
300 				if (FAIL == (ret = result->m.read_result_metadata(result, conn))) {
301 					/* For PS, we leave them in Prepared state */
302 					if (!stmt && conn->current_result) {
303 						mnd_efree(conn->current_result);
304 						conn->current_result = NULL;
305 					}
306 					DBG_ERR("Error occurred while reading metadata");
307 					break;
308 				}
309 
310 				/* Check for SERVER_STATUS_MORE_RESULTS if needed */
311 				conn->payload_decoder_factory->m.init_eof_packet(&fields_eof);
312 				if (FAIL == (ret = PACKET_READ(conn, &fields_eof))) {
313 					DBG_ERR("Error occurred while reading the EOF packet");
314 					result->m.free_result_contents(result);
315 					if (!stmt) {
316 						conn->current_result = NULL;
317 					} else {
318 						stmt->result = NULL;
319 						/* XXX: This will crash, because we will null also the methods.
320 							But seems it happens in extreme cases or doesn't. Should be fixed by exporting a function
321 							(from mysqlnd_driver.c?) to do the reset.
322 							This is done also in mysqlnd_ps.c
323 						*/
324 						memset(stmt, 0, sizeof(*stmt));
325 						stmt->state = MYSQLND_STMT_INITTED;
326 					}
327 				} else {
328 					DBG_INF_FMT("warnings=%u server_status=%u", fields_eof.warning_count, fields_eof.server_status);
329 					UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, fields_eof.warning_count);
330 					/*
331 					  If SERVER_MORE_RESULTS_EXISTS is set then this is either MULTI_QUERY or a CALL()
332 					  The first packet after sending the query/com_execute has the bit set only
333 					  in these cases. Not sure why it's a needed but it marks that the whole stream
334 					  will include many result sets. What actually matters are the bits set at the end
335 					  of every result set (the EOF packet).
336 					*/
337 					UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, fields_eof.server_status);
338 					if (fields_eof.server_status & SERVER_QUERY_NO_GOOD_INDEX_USED) {
339 						statistic = STAT_BAD_INDEX_USED;
340 					} else if (fields_eof.server_status & SERVER_QUERY_NO_INDEX_USED) {
341 						statistic = STAT_NO_INDEX_USED;
342 					} else if (fields_eof.server_status & SERVER_QUERY_WAS_SLOW) {
343 						statistic = STAT_QUERY_WAS_SLOW;
344 					}
345 					MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
346 				}
347 				PACKET_FREE(&fields_eof);
348 			} while (0);
349 			break; /* switch break */
350 		}
351 	} while (0);
352 	PACKET_FREE(&rset_header);
353 
354 	DBG_INF(ret == PASS? "PASS":"FAIL");
355 	DBG_RETURN(ret);
356 }
357 /* }}} */
358 
359 
360 /* {{{ mysqlnd_result_buffered::fetch_lengths */
361 /*
362   Do lazy initialization for buffered results. As PHP strings have
363   length inside, this function makes not much sense in the context
364   of PHP, to be called as separate function. But let's have it for
365   completeness.
366 */
367 static const size_t *
MYSQLND_METHOD(mysqlnd_result_buffered,fetch_lengths)368 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_lengths)(const MYSQLND_RES_BUFFERED * const result)
369 {
370 	DBG_ENTER("mysqlnd_result_buffered::fetch_lengths");
371 
372 	if (result->current_row > result->row_count || result->current_row == 0) {
373 		DBG_INF("EOF");
374 		DBG_RETURN(NULL); /* No more rows, or no fetched row */
375 	}
376 	DBG_INF("non NULL");
377 	DBG_RETURN(result->lengths);
378 }
379 /* }}} */
380 
381 
382 /* {{{ mysqlnd_result_unbuffered::fetch_lengths */
383 static const size_t *
MYSQLND_METHOD(mysqlnd_result_unbuffered,fetch_lengths)384 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths)(const MYSQLND_RES_UNBUFFERED * const result)
385 {
386 	/* simulate output of libmysql */
387 	return (result->last_row_buffer.ptr || result->eof_reached)? result->lengths : NULL;
388 }
389 /* }}} */
390 
391 
392 /* {{{ mysqlnd_res::fetch_lengths */
393 static const size_t *
MYSQLND_METHOD(mysqlnd_res,fetch_lengths)394 MYSQLND_METHOD(mysqlnd_res, fetch_lengths)(const MYSQLND_RES * const result)
395 {
396 	const size_t * ret;
397 	DBG_ENTER("mysqlnd_res::fetch_lengths");
398 	ret = result->stored_data && result->stored_data->m.fetch_lengths ?
399 					result->stored_data->m.fetch_lengths(result->stored_data) :
400 					(result->unbuf && result->unbuf->m.fetch_lengths ?
401 						result->unbuf->m.fetch_lengths(result->unbuf) :
402 						NULL
403 					);
404 	DBG_RETURN(ret);
405 }
406 /* }}} */
407 
408 
409 /* {{{ mysqlnd_result_unbuffered::fetch_row */
410 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_unbuffered,fetch_row)411 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)(MYSQLND_RES * result, zval **row_ptr, const unsigned int flags, bool * fetched_anything)
412 {
413 	enum_func_status ret;
414 	MYSQLND_PACKET_ROW	*row_packet = result->unbuf->row_packet;
415 	const MYSQLND_RES_METADATA * const meta = result->meta;
416 	MYSQLND_RES_UNBUFFERED *set = result->unbuf;
417 	MYSQLND_CONN_DATA * const conn = result->conn;
418 	void *checkpoint;
419 
420 	DBG_ENTER("mysqlnd_result_unbuffered::fetch_row");
421 
422 	*fetched_anything = FALSE;
423 	if (set->eof_reached) {
424 		/* No more rows obviously */
425 		DBG_RETURN(PASS);
426 	}
427 	if (GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA) {
428 		SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
429 		DBG_RETURN(FAIL);
430 	}
431 	if (!row_packet) {
432 		/* Not fully initialized object that is being cleaned up */
433 		DBG_RETURN(FAIL);
434 	}
435 
436 	checkpoint = result->memory_pool->checkpoint;
437 	mysqlnd_mempool_save_state(result->memory_pool);
438 
439 	if (PASS == (ret = PACKET_READ(conn, row_packet)) && !row_packet->eof) {
440 		set->last_row_buffer = row_packet->row_buffer;
441 		row_packet->row_buffer.ptr = NULL;
442 
443 		MYSQLND_INC_CONN_STATISTIC(conn->stats, set->stmt
444 			? STAT_ROWS_FETCHED_FROM_CLIENT_PS_UNBUF
445 			: STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
446 
447 		if (row_ptr) {
448 			unsigned int field_count = meta->field_count;
449 
450 			*row_ptr = result->row_data;
451 			enum_func_status rc = set->m.row_decoder(
452 				&set->last_row_buffer, result->row_data, field_count,
453 				row_packet->fields_metadata, conn->options->int_and_float_native, conn->stats);
454 			if (PASS != rc) {
455 				mysqlnd_mempool_restore_state(result->memory_pool);
456 				result->memory_pool->checkpoint = checkpoint;
457 				DBG_RETURN(FAIL);
458 			}
459 
460 			size_t *lengths = set->lengths;
461 			if (lengths) {
462 				for (unsigned i = 0; i < field_count; i++) {
463 					zval *data = &result->row_data[i];
464 					lengths[i] = Z_TYPE_P(data) == IS_STRING ? Z_STRLEN_P(data) : 0;
465 				}
466 			}
467 		}
468 		set->row_count++;
469 		*fetched_anything = TRUE;
470 	} else if (ret == FAIL) {
471 		if (row_packet->error_info.error_no) {
472 			COPY_CLIENT_ERROR(conn->error_info, row_packet->error_info);
473 			if (set->stmt) {
474 				COPY_CLIENT_ERROR(set->stmt->error_info, row_packet->error_info);
475 			}
476 			DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
477 		}
478 		if (GET_CONNECTION_STATE(&conn->state) != CONN_QUIT_SENT) {
479 			SET_CONNECTION_STATE(&conn->state, CONN_READY);
480 		}
481 		set->eof_reached = TRUE; /* so next time we won't get an error */
482 	} else if (row_packet->eof) {
483 		/* Mark the connection as usable again */
484 		DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
485 		set->eof_reached = TRUE;
486 
487 		UPSERT_STATUS_RESET(conn->upsert_status);
488 		UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
489 		UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
490 		/*
491 		  result->row_packet will be cleaned when
492 		  destroying the result object
493 		*/
494 		if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
495 			SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
496 		} else {
497 			SET_CONNECTION_STATE(&conn->state, CONN_READY);
498 		}
499 	}
500 
501 	mysqlnd_mempool_restore_state(result->memory_pool);
502 	result->memory_pool->checkpoint = checkpoint;
503 
504 	DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
505 	DBG_RETURN(ret);
506 }
507 /* }}} */
508 
509 
510 /* {{{ mysqlnd_res::use_result */
511 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,use_result)512 MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, MYSQLND_STMT_DATA *stmt)
513 {
514 	MYSQLND_CONN_DATA * const conn = result->conn;
515 	DBG_ENTER("mysqlnd_res::use_result");
516 
517 	SET_EMPTY_ERROR(conn->error_info);
518 
519 	if (!stmt) {
520 		result->type = MYSQLND_RES_NORMAL;
521 	} else {
522 		result->type = MYSQLND_RES_PS_UNBUF;
523 	}
524 
525 	result->unbuf = mysqlnd_result_unbuffered_init(result, result->field_count, stmt);
526 
527 	/*
528 	  Will be freed in the mysqlnd_internal_free_result_contents() called
529 	  by the resource destructor. mysqlnd_result_unbuffered::fetch_row() expects
530 	  this to be not NULL.
531 	*/
532 	/* FALSE = non-persistent */
533 	{
534 		struct st_mysqlnd_packet_row *row_packet = mnd_emalloc(sizeof(struct st_mysqlnd_packet_row));
535 
536 		conn->payload_decoder_factory->m.init_row_packet(row_packet);
537 		row_packet->result_set_memory_pool = result->unbuf->result_set_memory_pool;
538 		row_packet->field_count = result->field_count;
539 		row_packet->binary_protocol = stmt != NULL;
540 		row_packet->fields_metadata = result->meta->fields;
541 
542 		result->unbuf->row_packet = row_packet;
543 	}
544 
545 	DBG_RETURN(result);
546 }
547 /* }}} */
548 
549 
550 /* {{{ mysqlnd_result_buffered::fetch_row */
551 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered,fetch_row)552 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row)(MYSQLND_RES * result, zval **row_ptr, const unsigned int flags, bool * fetched_anything)
553 {
554 	MYSQLND_RES_BUFFERED *set = result->stored_data;
555 
556 	DBG_ENTER("mysqlnd_result_buffered::fetch_row");
557 
558 	/* If we haven't read everything */
559 	if (set->current_row < set->row_count) {
560 		if (row_ptr) {
561 			const MYSQLND_RES_METADATA * const meta = result->meta;
562 			const unsigned int field_count = meta->field_count;
563 			MYSQLND_CONN_DATA * const conn = result->conn;
564 			enum_func_status rc;
565 			zval *current_row = result->row_data;
566 			*row_ptr = result->row_data;
567 			rc = result->stored_data->m.row_decoder(&set->row_buffers[set->current_row],
568 													current_row,
569 													field_count,
570 													meta->fields,
571 													conn->options->int_and_float_native,
572 													conn->stats);
573 			if (rc != PASS) {
574 				DBG_RETURN(FAIL);
575 			}
576 
577 			if (set->lengths) {
578 				for (unsigned i = 0; i < field_count; ++i) {
579 					zval *data = &current_row[i];
580 					set->lengths[i] = Z_TYPE_P(data) == IS_STRING ? Z_STRLEN_P(data) : 0;
581 				}
582 			}
583 		}
584 
585 		++set->current_row;
586 		MYSQLND_INC_GLOBAL_STATISTIC(set->stmt
587 			? STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF : STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
588 		*fetched_anything = TRUE;
589 	} else {
590 		if (set->current_row == set->row_count) {
591 			set->current_row = set->row_count + 1;
592 		}
593 		DBG_INF_FMT("EOF reached. current_row=%llu", (unsigned long long) set->current_row);
594 		*fetched_anything = FALSE;
595 	}
596 
597 	DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
598 	DBG_RETURN(PASS);
599 }
600 /* }}} */
601 
602 
603 /* {{{ mysqlnd_res::fetch_row */
604 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,fetch_row)605 MYSQLND_METHOD(mysqlnd_res, fetch_row)(MYSQLND_RES *result, zval **row_ptr, const unsigned int flags, bool *fetched_anything)
606 {
607 	const mysqlnd_fetch_row_func f =
608 		result->stored_data ? result->stored_data->m.fetch_row :
609 		result->unbuf ? result->unbuf->m.fetch_row : NULL;
610 	if (f) {
611 		return f(result, row_ptr, flags, fetched_anything);
612 	}
613 	*fetched_anything = FALSE;
614 	return PASS;
615 }
616 /* }}} */
617 
618 
619 /* {{{ mysqlnd_res::store_result_fetch_data */
620 enum_func_status
MYSQLND_METHOD(mysqlnd_res,store_result_fetch_data)621 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result,
622 													MYSQLND_RES_METADATA * meta,
623 													MYSQLND_ROW_BUFFER **row_buffers,
624 													bool binary_protocol)
625 {
626 	enum_func_status ret;
627 	uint64_t total_allocated_rows = 0;
628 	unsigned int free_rows = 0;
629 	MYSQLND_RES_BUFFERED * set = result->stored_data;
630 	MYSQLND_PACKET_ROW row_packet;
631 
632 	DBG_ENTER("mysqlnd_res::store_result_fetch_data");
633 	if (!set || !row_buffers) {
634 		ret = FAIL;
635 		goto end;
636 	}
637 
638 	*row_buffers = NULL;
639 
640 	conn->payload_decoder_factory->m.init_row_packet(&row_packet);
641 	set->references	= 1;
642 
643 	row_packet.result_set_memory_pool = result->stored_data->result_set_memory_pool;
644 	row_packet.field_count = meta->field_count;
645 	row_packet.binary_protocol = binary_protocol;
646 	row_packet.fields_metadata = meta->fields;
647 
648 	while (FAIL != (ret = PACKET_READ(conn, &row_packet)) && !row_packet.eof) {
649 		if (!free_rows) {
650 			MYSQLND_ROW_BUFFER * new_row_buffers;
651 
652 			if (total_allocated_rows < 1024) {
653 				if (total_allocated_rows == 0) {
654 					free_rows = 1;
655 					total_allocated_rows = 1;
656 				} else {
657 					free_rows = total_allocated_rows;
658 					total_allocated_rows *= 2;
659 				}
660 			} else {
661 				free_rows = 1024;
662 				total_allocated_rows += 1024;
663 			}
664 
665 			/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
666 			if (total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER) > SIZE_MAX) {
667 				SET_OOM_ERROR(conn->error_info);
668 				ret = FAIL;
669 				goto free_end;
670 			}
671 			if (*row_buffers) {
672 				new_row_buffers = mnd_erealloc(*row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER)));
673 			} else {
674 				new_row_buffers = mnd_emalloc((size_t)(total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER)));
675 			}
676 			*row_buffers = new_row_buffers;
677 		}
678 		free_rows--;
679 		(*row_buffers)[set->row_count] = row_packet.row_buffer;
680 
681 		set->row_count++;
682 
683 		/* So row_packet's destructor function won't efree() it */
684 		row_packet.row_buffer.ptr = NULL;
685 	}
686 	/* Overflow ? */
687 	MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats,
688 									   binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
689 														STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
690 									   set->row_count);
691 
692 	/* Finally clean */
693 	if (row_packet.eof) {
694 		UPSERT_STATUS_RESET(conn->upsert_status);
695 		UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet.warning_count);
696 		UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet.server_status);
697 	}
698 
699 	if (ret == FAIL) {
700 		/* Error packets do not contain server status information. However, we know that after
701 		 * an error there will be no further result sets. */
702 		UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status,
703 			UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & ~SERVER_MORE_RESULTS_EXISTS);
704 	}
705 
706 	/* save some memory */
707 	if (free_rows) {
708 		/* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
709 		if (set->row_count * sizeof(MYSQLND_ROW_BUFFER) > SIZE_MAX) {
710 			SET_OOM_ERROR(conn->error_info);
711 			ret = FAIL;
712 			goto free_end;
713 		}
714 		*row_buffers = mnd_erealloc(*row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_ROW_BUFFER)));
715 	}
716 
717 	if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
718 		SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
719 	} else {
720 		SET_CONNECTION_STATE(&conn->state, CONN_READY);
721 	}
722 
723 	if (ret == FAIL) {
724 		COPY_CLIENT_ERROR(&set->error_info, row_packet.error_info);
725 	} else {
726 		/* libmysql's documentation says it should be so for SELECT statements */
727 		UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, set->row_count);
728 	}
729 	DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u",
730 				ret == PASS? "PASS":"FAIL",
731 				(uint32_t) set->row_count,
732 				UPSERT_STATUS_GET_WARNINGS(conn->upsert_status),
733 				UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status));
734 free_end:
735 	PACKET_FREE(&row_packet);
736 	DBG_INF_FMT("rows=%llu", (unsigned long long)set->row_count);
737 end:
738 	DBG_RETURN(ret);
739 }
740 /* }}} */
741 
742 
743 /* {{{ mysqlnd_res::store_result */
744 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,store_result)745 MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
746 										  MYSQLND_CONN_DATA * const conn,
747 										  MYSQLND_STMT_DATA *stmt)
748 {
749 	enum_func_status ret;
750 	MYSQLND_ROW_BUFFER **row_buffers = NULL;
751 
752 	DBG_ENTER("mysqlnd_res::store_result");
753 
754 	/* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
755 	/* In case of error the reference will be released in free_result() called indirectly by our caller */
756 	result->conn = conn->m->get_reference(conn);
757 	result->type = MYSQLND_RES_NORMAL;
758 
759 	SET_CONNECTION_STATE(&conn->state, CONN_FETCHING_DATA);
760 
761 	result->stored_data	= (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_init(result, result->field_count, stmt);
762 	row_buffers = &result->stored_data->row_buffers;
763 
764 	ret = result->m.store_result_fetch_data(conn, result, result->meta, row_buffers, stmt != NULL);
765 
766 	if (FAIL == ret) {
767 		if (result->stored_data) {
768 			COPY_CLIENT_ERROR(conn->error_info, result->stored_data->error_info);
769 		} else {
770 			SET_OOM_ERROR(conn->error_info);
771 		}
772 		DBG_RETURN(NULL);
773 	} else {
774 		result->stored_data->current_row = 0;
775 	}
776 
777 	/* libmysql's documentation says it should be so for SELECT statements */
778 	UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, result->stored_data->row_count);
779 
780 	DBG_RETURN(result);
781 }
782 /* }}} */
783 
784 
785 /* {{{ mysqlnd_res::skip_result */
786 static void
MYSQLND_METHOD(mysqlnd_res,skip_result)787 MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result)
788 {
789 	bool fetched_anything;
790 
791 	DBG_ENTER("mysqlnd_res::skip_result");
792 	/*
793 	  Unbuffered sets
794 	  A PS could be prepared - there is metadata and thus a stmt->result but the
795 	  fetch_row function isn't actually set (NULL), thus we have to skip these.
796 	*/
797 	if (result->unbuf && !result->unbuf->eof_reached) {
798 		MYSQLND_CONN_DATA * const conn = result->conn;
799 		DBG_INF("skipping result");
800 		/* We have to fetch all data to clean the line */
801 		MYSQLND_INC_CONN_STATISTIC(conn->stats,
802 									result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS:
803 																		STAT_FLUSHED_PS_SETS);
804 
805 		while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything)) && fetched_anything == TRUE) {
806 			MYSQLND_INC_CONN_STATISTIC(conn->stats,
807 				result->type == MYSQLND_RES_NORMAL
808 					? STAT_ROWS_SKIPPED_NORMAL : STAT_ROWS_SKIPPED_PS);
809 		}
810 	}
811 	DBG_VOID_RETURN;
812 }
813 /* }}} */
814 
815 
816 /* {{{ mysqlnd_res::free_result */
817 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,free_result)818 MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, const bool implicit)
819 {
820 	DBG_ENTER("mysqlnd_res::free_result");
821 
822 	MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL,
823 							   implicit == TRUE?	STAT_FREE_RESULT_IMPLICIT:
824 							   						STAT_FREE_RESULT_EXPLICIT);
825 
826 	result->m.skip_result(result);
827 	result->m.free_result_contents(result);
828 	DBG_RETURN(PASS);
829 }
830 /* }}} */
831 
832 
833 /* {{{ mysqlnd_res::data_seek */
834 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,data_seek)835 MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * const result, const uint64_t row)
836 {
837 	DBG_ENTER("mysqlnd_res::data_seek");
838 	DBG_INF_FMT("row=%" PRIu64, row);
839 
840 	DBG_RETURN(result->stored_data? result->stored_data->m.data_seek(result->stored_data, row) : FAIL);
841 }
842 /* }}} */
843 
844 
845 /* {{{ mysqlnd_result_buffered::data_seek */
846 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered,data_seek)847 MYSQLND_METHOD(mysqlnd_result_buffered, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row)
848 {
849 	DBG_ENTER("mysqlnd_result_buffered::data_seek");
850 
851 	/* libmysql just moves to the end, it does traversing of a linked list */
852 	if (row >= result->row_count) {
853 		result->current_row = result->row_count;
854 	} else {
855 		result->current_row = row;
856 	}
857 	DBG_RETURN(PASS);
858 }
859 /* }}} */
860 
861 
862 /* {{{ mysqlnd_result_unbuffered::num_rows */
863 static uint64_t
MYSQLND_METHOD(mysqlnd_result_unbuffered,num_rows)864 MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows)(const MYSQLND_RES_UNBUFFERED * const result)
865 {
866 	/* Be compatible with libmysql. We count row_count, but will return 0 */
867 	return result->eof_reached? result->row_count : 0;
868 }
869 /* }}} */
870 
871 
872 /* {{{ mysqlnd_result_buffered::num_rows */
873 static uint64_t
MYSQLND_METHOD(mysqlnd_result_buffered,num_rows)874 MYSQLND_METHOD(mysqlnd_result_buffered, num_rows)(const MYSQLND_RES_BUFFERED * const result)
875 {
876 	return result->row_count;
877 }
878 /* }}} */
879 
880 
881 /* {{{ mysqlnd_res::num_rows */
882 static uint64_t
MYSQLND_METHOD(mysqlnd_res,num_rows)883 MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result)
884 {
885 	return result->stored_data?
886 			result->stored_data->m.num_rows(result->stored_data) :
887 			(result->unbuf? result->unbuf->m.num_rows(result->unbuf) : 0);
888 }
889 /* }}} */
890 
891 
892 /* {{{ mysqlnd_res::num_fields */
893 static unsigned int
MYSQLND_METHOD(mysqlnd_res,num_fields)894 MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result)
895 {
896 	return result->field_count;
897 }
898 /* }}} */
899 
900 
901 /* {{{ mysqlnd_res::fetch_field */
902 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field)903 MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result)
904 {
905 	DBG_ENTER("mysqlnd_res::fetch_field");
906 	do {
907 		if (result->meta) {
908 			DBG_RETURN(result->meta->m->fetch_field(result->meta));
909 		}
910 	} while (0);
911 	DBG_RETURN(NULL);
912 }
913 /* }}} */
914 
915 
916 /* {{{ mysqlnd_res::fetch_field_direct */
917 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field_direct)918 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET fieldnr)
919 {
920 	DBG_ENTER("mysqlnd_res::fetch_field_direct");
921 	do {
922 		if (result->meta) {
923 			DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr));
924 		}
925 	} while (0);
926 
927 	DBG_RETURN(NULL);
928 }
929 /* }}} */
930 
931 
932 /* {{{ mysqlnd_res::fetch_field */
933 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_fields)934 MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result)
935 {
936 	DBG_ENTER("mysqlnd_res::fetch_fields");
937 	do {
938 		if (result->meta) {
939 			DBG_RETURN(result->meta->m->fetch_fields(result->meta));
940 		}
941 	} while (0);
942 	DBG_RETURN(NULL);
943 }
944 /* }}} */
945 
946 
947 /* {{{ mysqlnd_res::field_seek */
948 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_seek)949 MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET field_offset)
950 {
951 	return result->meta? result->meta->m->field_seek(result->meta, field_offset) : 0;
952 }
953 /* }}} */
954 
955 
956 /* {{{ mysqlnd_res::field_tell */
957 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_tell)958 MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result)
959 {
960 	return result->meta? result->meta->m->field_tell(result->meta) : 0;
961 }
962 /* }}} */
963 
964 
965 /* {{{ mysqlnd_res::fetch_into */
966 static void
MYSQLND_METHOD(mysqlnd_res,fetch_into)967 MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, const unsigned int flags,
968 										zval *return_value ZEND_FILE_LINE_DC)
969 {
970 	bool fetched_anything;
971 	zval *row_data;
972 
973 	DBG_ENTER("mysqlnd_res::fetch_into");
974 	if (FAIL == result->m.fetch_row(result, &row_data, flags, &fetched_anything)) {
975 		RETVAL_FALSE;
976 		DBG_VOID_RETURN;
977 	} else if (fetched_anything == FALSE) {
978 		RETVAL_NULL();
979 		DBG_VOID_RETURN;
980 	}
981 
982 	const MYSQLND_RES_METADATA * const meta = result->meta;
983 	unsigned int array_size = meta->field_count;
984 	if ((flags & (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) == (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) {
985 		array_size *= 2;
986 	}
987 	array_init_size(return_value, array_size);
988 
989 	HashTable *row_ht = Z_ARRVAL_P(return_value);
990 	MYSQLND_FIELD *field = meta->fields;
991 	for (unsigned i = 0; i < meta->field_count; i++, field++) {
992 		zval *data = &row_data[i];
993 
994 		if (flags & MYSQLND_FETCH_NUM) {
995 			if (zend_hash_index_add(row_ht, i, data) != NULL) {
996 				Z_TRY_ADDREF_P(data);
997 			}
998 		}
999 		if (flags & MYSQLND_FETCH_ASSOC) {
1000 			/* zend_hash_quick_update needs length + trailing zero */
1001 			/* QQ: Error handling ? */
1002 			/*
1003 			  zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1004 			  the index is a numeric and convert it to it. This however means constant
1005 			  hashing of the column name, which is not needed as it can be precomputed.
1006 			*/
1007 			Z_TRY_ADDREF_P(data);
1008 			if (meta->fields[i].is_numeric == FALSE) {
1009 				zend_hash_update(row_ht, meta->fields[i].sname, data);
1010 			} else {
1011 				zend_hash_index_update(row_ht, meta->fields[i].num_key, data);
1012 			}
1013 		}
1014 
1015 		zval_ptr_dtor_nogc(data);
1016 	}
1017 	DBG_VOID_RETURN;
1018 }
1019 /* }}} */
1020 
1021 
1022 /* {{{ mysqlnd_res::fetch_row_c */
1023 static MYSQLND_ROW_C
MYSQLND_METHOD(mysqlnd_res,fetch_row_c)1024 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result)
1025 {
1026 	bool fetched_anything;
1027 	zval *row_data;
1028 	MYSQLND_ROW_C ret = NULL;
1029 	DBG_ENTER("mysqlnd_res::fetch_row_c");
1030 
1031 	mysqlnd_result_free_prev_data(result);
1032 	if (result->m.fetch_row(result, &row_data, 0, &fetched_anything) == PASS && fetched_anything) {
1033 		unsigned field_count = result->field_count;
1034 		MYSQLND_FIELD *field = result->meta->fields;
1035 
1036 		ret = mnd_emalloc(field_count * sizeof(char *));
1037 		for (unsigned i = 0; i < field_count; i++, field++) {
1038 			zval *data = &row_data[i];
1039 			if (Z_TYPE_P(data) != IS_NULL) {
1040 				convert_to_string(data);
1041 				ret[i] = Z_STRVAL_P(data);
1042 			} else {
1043 				ret[i] = NULL;
1044 			}
1045 		}
1046 		result->free_row_data = 1;
1047 	}
1048 	DBG_RETURN(ret);
1049 }
1050 /* }}} */
1051 
1052 
1053 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1054 	MYSQLND_METHOD(mysqlnd_res, fetch_row),
1055 	MYSQLND_METHOD(mysqlnd_res, use_result),
1056 	MYSQLND_METHOD(mysqlnd_res, store_result),
1057 	MYSQLND_METHOD(mysqlnd_res, fetch_into),
1058 	MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1059 	MYSQLND_METHOD(mysqlnd_res, num_rows),
1060 	MYSQLND_METHOD(mysqlnd_res, num_fields),
1061 	MYSQLND_METHOD(mysqlnd_res, skip_result),
1062 	MYSQLND_METHOD(mysqlnd_res, data_seek),
1063 	MYSQLND_METHOD(mysqlnd_res, field_seek),
1064 	MYSQLND_METHOD(mysqlnd_res, field_tell),
1065 	MYSQLND_METHOD(mysqlnd_res, fetch_field),
1066 	MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1067 	MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1068 	MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1069 	MYSQLND_METHOD(mysqlnd_res, fetch_lengths),
1070 	MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1071 	MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1072 	MYSQLND_METHOD(mysqlnd_res, free_result),
1073 	MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal),
1074 	mysqlnd_result_meta_init,
1075 	NULL, /* unused1 */
1076 	NULL, /* unused2 */
1077 	NULL, /* unused3 */
1078 	NULL, /* unused4 */
1079 	NULL  /* unused5 */
1080 MYSQLND_CLASS_METHODS_END;
1081 
1082 
1083 MYSQLND_CLASS_METHODS_START(mysqlnd_result_unbuffered)
1084 	MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row),
1085 	NULL, /* row_decoder */
1086 	MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows),
1087 	MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths),
1088 	MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)
1089 MYSQLND_CLASS_METHODS_END;
1090 
1091 
1092 MYSQLND_CLASS_METHODS_START(mysqlnd_result_buffered)
1093 	MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row),
1094 	NULL, /* row_decoder */
1095 	MYSQLND_METHOD(mysqlnd_result_buffered, num_rows),
1096 	MYSQLND_METHOD(mysqlnd_result_buffered, fetch_lengths),
1097 	MYSQLND_METHOD(mysqlnd_result_buffered, data_seek),
1098 	MYSQLND_METHOD(mysqlnd_result_buffered, free_result)
1099 MYSQLND_CLASS_METHODS_END;
1100 
1101 
1102 /* {{{ mysqlnd_result_init */
1103 PHPAPI MYSQLND_RES *
mysqlnd_result_init(const unsigned int field_count)1104 mysqlnd_result_init(const unsigned int field_count)
1105 {
1106 	const size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1107 	MYSQLND_MEMORY_POOL * pool;
1108 	MYSQLND_RES * ret;
1109 
1110 	DBG_ENTER("mysqlnd_result_init");
1111 
1112 	pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size));
1113 	if (!pool) {
1114 		DBG_RETURN(NULL);
1115 	}
1116 
1117 	ret = pool->get_chunk(pool, alloc_size);
1118 	memset(ret, 0, alloc_size);
1119 
1120 	ret->row_data = pool->get_chunk(pool, field_count * sizeof(zval));
1121 	ret->free_row_data = 0;
1122 
1123 	ret->memory_pool	= pool;
1124 	ret->field_count	= field_count;
1125 	ret->m = *mysqlnd_result_get_methods();
1126 
1127 	mysqlnd_mempool_save_state(pool);
1128 
1129 	DBG_RETURN(ret);
1130 }
1131 /* }}} */
1132 
1133 
1134 /* {{{ mysqlnd_result_unbuffered_init */
1135 PHPAPI MYSQLND_RES_UNBUFFERED *
mysqlnd_result_unbuffered_init(MYSQLND_RES * result,const unsigned int field_count,MYSQLND_STMT_DATA * stmt)1136 mysqlnd_result_unbuffered_init(MYSQLND_RES *result, const unsigned int field_count, MYSQLND_STMT_DATA *stmt)
1137 {
1138 	const size_t alloc_size = sizeof(MYSQLND_RES_UNBUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1139 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1140 	MYSQLND_RES_UNBUFFERED * ret;
1141 
1142 	DBG_ENTER("mysqlnd_result_unbuffered_init");
1143 
1144 	ret = pool->get_chunk(pool, alloc_size);
1145 	memset(ret, 0, alloc_size);
1146 
1147 	ret->result_set_memory_pool = pool;
1148 	ret->field_count = field_count;
1149 	ret->stmt = stmt;
1150 
1151 	ret->m = *mysqlnd_result_unbuffered_get_methods();
1152 
1153 	if (stmt) {
1154 		ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
1155 		ret->m.fetch_lengths = NULL; /* makes no sense */
1156 		ret->lengths = NULL;
1157 	} else {
1158 		ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
1159 
1160 		ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1161 		memset(ret->lengths, 0, field_count * sizeof(size_t));
1162 	}
1163 
1164 	DBG_RETURN(ret);
1165 }
1166 /* }}} */
1167 
1168 
1169 /* {{{ mysqlnd_result_buffered_init */
1170 PHPAPI MYSQLND_RES_BUFFERED *
mysqlnd_result_buffered_init(MYSQLND_RES * result,const unsigned int field_count,MYSQLND_STMT_DATA * stmt)1171 mysqlnd_result_buffered_init(MYSQLND_RES * result, const unsigned int field_count, MYSQLND_STMT_DATA *stmt)
1172 {
1173 	const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1174 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1175 	MYSQLND_RES_BUFFERED * ret;
1176 
1177 	DBG_ENTER("mysqlnd_result_buffered_init");
1178 
1179 	ret = pool->get_chunk(pool, alloc_size);
1180 	memset(ret, 0, alloc_size);
1181 
1182 	mysqlnd_error_info_init(&ret->error_info, /* persistent */ 0);
1183 
1184 	ret->result_set_memory_pool = pool;
1185 	ret->field_count= field_count;
1186 	ret->stmt = stmt;
1187 	ret->m = *mysqlnd_result_buffered_get_methods();
1188 
1189 	if (stmt) {
1190 		ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
1191 		ret->m.fetch_lengths = NULL; /* makes no sense */
1192 		ret->lengths = NULL;
1193 	} else {
1194 		ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
1195 
1196 		ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1197 		memset(ret->lengths, 0, field_count * sizeof(size_t));
1198 	}
1199 
1200 	DBG_RETURN(ret);
1201 }
1202 /* }}} */
1203