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