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