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