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