xref: /PHP-8.2/ext/mysqlnd/mysqlnd_result.c (revision b1211c1e)
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 						conn->current_result->m.free_result(conn->current_result, TRUE);
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 	DBG_INF_FMT("rows=%llu", (unsigned long long)set->row_count);
739 end:
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 void
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_VOID_RETURN;
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 	// We clean the error here because in unbuffered mode we could receive a new error
976 	// and therefore consumers of this method are checking for errors
977 	MYSQLND_CONN_DATA *conn = result->conn;
978 	if (conn) {
979 		SET_EMPTY_ERROR(conn->error_info);
980 	}
981 
982 	DBG_ENTER("mysqlnd_res::fetch_into");
983 	if (FAIL == result->m.fetch_row(result, &row_data, flags, &fetched_anything)) {
984 		RETVAL_FALSE;
985 		DBG_VOID_RETURN;
986 	} else if (fetched_anything == FALSE) {
987 		RETVAL_NULL();
988 		DBG_VOID_RETURN;
989 	}
990 
991 	const MYSQLND_RES_METADATA * const meta = result->meta;
992 	unsigned int array_size = meta->field_count;
993 	if ((flags & (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) == (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) {
994 		array_size *= 2;
995 	}
996 	array_init_size(return_value, array_size);
997 
998 	HashTable *row_ht = Z_ARRVAL_P(return_value);
999 	MYSQLND_FIELD *field = meta->fields;
1000 	for (unsigned i = 0; i < meta->field_count; i++, field++) {
1001 		zval *data = &row_data[i];
1002 
1003 		if (flags & MYSQLND_FETCH_NUM) {
1004 			if (zend_hash_index_add(row_ht, i, data) != NULL) {
1005 				Z_TRY_ADDREF_P(data);
1006 			}
1007 		}
1008 		if (flags & MYSQLND_FETCH_ASSOC) {
1009 			/* zend_hash_quick_update needs length + trailing zero */
1010 			/* QQ: Error handling ? */
1011 			/*
1012 			  zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1013 			  the index is a numeric and convert it to it. This however means constant
1014 			  hashing of the column name, which is not needed as it can be precomputed.
1015 			*/
1016 			Z_TRY_ADDREF_P(data);
1017 			if (meta->fields[i].is_numeric == FALSE) {
1018 				zend_hash_update(row_ht, meta->fields[i].sname, data);
1019 			} else {
1020 				zend_hash_index_update(row_ht, meta->fields[i].num_key, data);
1021 			}
1022 		}
1023 
1024 		zval_ptr_dtor_nogc(data);
1025 	}
1026 	DBG_VOID_RETURN;
1027 }
1028 /* }}} */
1029 
1030 
1031 /* {{{ mysqlnd_res::fetch_row_c */
1032 static MYSQLND_ROW_C
MYSQLND_METHOD(mysqlnd_res,fetch_row_c)1033 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result)
1034 {
1035 	bool fetched_anything;
1036 	zval *row_data;
1037 	MYSQLND_ROW_C ret = NULL;
1038 	DBG_ENTER("mysqlnd_res::fetch_row_c");
1039 
1040 	mysqlnd_result_free_prev_data(result);
1041 	if (result->m.fetch_row(result, &row_data, 0, &fetched_anything) == PASS && fetched_anything) {
1042 		unsigned field_count = result->field_count;
1043 		MYSQLND_FIELD *field = result->meta->fields;
1044 
1045 		ret = mnd_emalloc(field_count * sizeof(char *));
1046 		for (unsigned i = 0; i < field_count; i++, field++) {
1047 			zval *data = &row_data[i];
1048 			if (Z_TYPE_P(data) != IS_NULL) {
1049 				convert_to_string(data);
1050 				ret[i] = Z_STRVAL_P(data);
1051 			} else {
1052 				ret[i] = NULL;
1053 			}
1054 		}
1055 		result->free_row_data = 1;
1056 	}
1057 	DBG_RETURN(ret);
1058 }
1059 /* }}} */
1060 
1061 
1062 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1063 	MYSQLND_METHOD(mysqlnd_res, fetch_row),
1064 	MYSQLND_METHOD(mysqlnd_res, use_result),
1065 	MYSQLND_METHOD(mysqlnd_res, store_result),
1066 	MYSQLND_METHOD(mysqlnd_res, fetch_into),
1067 	MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1068 	MYSQLND_METHOD(mysqlnd_res, num_rows),
1069 	MYSQLND_METHOD(mysqlnd_res, num_fields),
1070 	MYSQLND_METHOD(mysqlnd_res, skip_result),
1071 	MYSQLND_METHOD(mysqlnd_res, data_seek),
1072 	MYSQLND_METHOD(mysqlnd_res, field_seek),
1073 	MYSQLND_METHOD(mysqlnd_res, field_tell),
1074 	MYSQLND_METHOD(mysqlnd_res, fetch_field),
1075 	MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1076 	MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1077 	MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1078 	MYSQLND_METHOD(mysqlnd_res, fetch_lengths),
1079 	MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1080 	MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1081 	MYSQLND_METHOD(mysqlnd_res, free_result),
1082 	MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal),
1083 	mysqlnd_result_meta_init,
1084 	NULL, /* unused1 */
1085 	NULL, /* unused2 */
1086 	NULL, /* unused3 */
1087 	NULL, /* unused4 */
1088 	NULL  /* unused5 */
1089 MYSQLND_CLASS_METHODS_END;
1090 
1091 
1092 MYSQLND_CLASS_METHODS_START(mysqlnd_result_unbuffered)
1093 	MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row),
1094 	NULL, /* row_decoder */
1095 	MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows),
1096 	MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths),
1097 	MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)
1098 MYSQLND_CLASS_METHODS_END;
1099 
1100 
1101 MYSQLND_CLASS_METHODS_START(mysqlnd_result_buffered)
1102 	MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row),
1103 	NULL, /* row_decoder */
1104 	MYSQLND_METHOD(mysqlnd_result_buffered, num_rows),
1105 	MYSQLND_METHOD(mysqlnd_result_buffered, fetch_lengths),
1106 	MYSQLND_METHOD(mysqlnd_result_buffered, data_seek),
1107 	MYSQLND_METHOD(mysqlnd_result_buffered, free_result)
1108 MYSQLND_CLASS_METHODS_END;
1109 
1110 
1111 /* {{{ mysqlnd_result_init */
1112 PHPAPI MYSQLND_RES *
mysqlnd_result_init(const unsigned int field_count)1113 mysqlnd_result_init(const unsigned int field_count)
1114 {
1115 	const size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1116 	MYSQLND_MEMORY_POOL * pool;
1117 	MYSQLND_RES * ret;
1118 
1119 	DBG_ENTER("mysqlnd_result_init");
1120 
1121 	pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size));
1122 	if (!pool) {
1123 		DBG_RETURN(NULL);
1124 	}
1125 
1126 	ret = pool->get_chunk(pool, alloc_size);
1127 	memset(ret, 0, alloc_size);
1128 
1129 	ret->row_data = pool->get_chunk(pool, field_count * sizeof(zval));
1130 	ret->free_row_data = 0;
1131 
1132 	ret->memory_pool	= pool;
1133 	ret->field_count	= field_count;
1134 	ret->m = *mysqlnd_result_get_methods();
1135 
1136 	mysqlnd_mempool_save_state(pool);
1137 
1138 	DBG_RETURN(ret);
1139 }
1140 /* }}} */
1141 
1142 
1143 /* {{{ mysqlnd_result_unbuffered_init */
1144 PHPAPI MYSQLND_RES_UNBUFFERED *
mysqlnd_result_unbuffered_init(MYSQLND_RES * result,const unsigned int field_count,MYSQLND_STMT_DATA * stmt)1145 mysqlnd_result_unbuffered_init(MYSQLND_RES *result, const unsigned int field_count, MYSQLND_STMT_DATA *stmt)
1146 {
1147 	const size_t alloc_size = sizeof(MYSQLND_RES_UNBUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1148 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1149 	MYSQLND_RES_UNBUFFERED * ret;
1150 
1151 	DBG_ENTER("mysqlnd_result_unbuffered_init");
1152 
1153 	ret = pool->get_chunk(pool, alloc_size);
1154 	memset(ret, 0, alloc_size);
1155 
1156 	ret->result_set_memory_pool = pool;
1157 	ret->field_count = field_count;
1158 	ret->stmt = stmt;
1159 
1160 	ret->m = *mysqlnd_result_unbuffered_get_methods();
1161 
1162 	if (stmt) {
1163 		ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
1164 		ret->m.fetch_lengths = NULL; /* makes no sense */
1165 		ret->lengths = NULL;
1166 	} else {
1167 		ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
1168 
1169 		ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1170 		memset(ret->lengths, 0, field_count * sizeof(size_t));
1171 	}
1172 
1173 	DBG_RETURN(ret);
1174 }
1175 /* }}} */
1176 
1177 
1178 /* {{{ mysqlnd_result_buffered_init */
1179 PHPAPI MYSQLND_RES_BUFFERED *
mysqlnd_result_buffered_init(MYSQLND_RES * result,const unsigned int field_count,MYSQLND_STMT_DATA * stmt)1180 mysqlnd_result_buffered_init(MYSQLND_RES * result, const unsigned int field_count, MYSQLND_STMT_DATA *stmt)
1181 {
1182 	const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1183 	MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1184 	MYSQLND_RES_BUFFERED * ret;
1185 
1186 	DBG_ENTER("mysqlnd_result_buffered_init");
1187 
1188 	ret = pool->get_chunk(pool, alloc_size);
1189 	memset(ret, 0, alloc_size);
1190 
1191 	mysqlnd_error_info_init(&ret->error_info, /* persistent */ 0);
1192 
1193 	ret->result_set_memory_pool = pool;
1194 	ret->field_count= field_count;
1195 	ret->stmt = stmt;
1196 	ret->m = *mysqlnd_result_buffered_get_methods();
1197 
1198 	if (stmt) {
1199 		ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
1200 		ret->m.fetch_lengths = NULL; /* makes no sense */
1201 		ret->lengths = NULL;
1202 	} else {
1203 		ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
1204 
1205 		ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1206 		memset(ret->lengths, 0, field_count * sizeof(size_t));
1207 	}
1208 
1209 	DBG_RETURN(ret);
1210 }
1211 /* }}} */
1212