1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2006-2018 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 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 "MYSQLND_LLU_SPEC" 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 DBG_VOID_RETURN;
305 }
306 /* }}} */
307
308
309 /* {{{ mysqlnd_res::free_result_internal */
310 static
MYSQLND_METHOD(mysqlnd_res,free_result_internal)311 void MYSQLND_METHOD(mysqlnd_res, free_result_internal)(MYSQLND_RES * result)
312 {
313 DBG_ENTER("mysqlnd_res::free_result_internal");
314 result->m.skip_result(result);
315
316 result->m.free_result_contents(result);
317
318 if (result->conn) {
319 result->conn->m->free_reference(result->conn);
320 result->conn = NULL;
321 }
322
323 mysqlnd_mempool_destroy(result->memory_pool);
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->m.free_result_contents(result);
358 DBG_RETURN(FAIL);
359 }
360 /* COM_FIELD_LIST is broken and has premature EOF, thus we need to hack here and in mysqlnd_res_meta.c */
361 result->field_count = result->meta->field_count;
362
363 /*
364 2. Follows an EOF packet, which the client of mysqlnd_read_result_metadata()
365 should consume.
366 3. If there is a result set, it follows. The last packet will have 'eof' set
367 If PS, then no result set follows.
368 */
369
370 DBG_RETURN(PASS);
371 }
372 /* }}} */
373
374
375 /* {{{ mysqlnd_query_read_result_set_header */
376 enum_func_status
mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn,MYSQLND_STMT * s)377 mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s)
378 {
379 enum_func_status ret;
380 MYSQLND_STMT_DATA * stmt = s ? s->data : NULL;
381 MYSQLND_PACKET_RSET_HEADER rset_header;
382 MYSQLND_PACKET_EOF fields_eof;
383
384 DBG_ENTER("mysqlnd_query_read_result_set_header");
385 DBG_INF_FMT("stmt=%lu", stmt? stmt->stmt_id:0);
386
387 ret = FAIL;
388 do {
389 conn->payload_decoder_factory->m.init_rset_header_packet(&rset_header);
390 UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
391
392 if (FAIL == (ret = PACKET_READ(conn, &rset_header))) {
393 php_error_docref(NULL, E_WARNING, "Error reading result set's header");
394 break;
395 }
396
397 if (rset_header.error_info.error_no) {
398 /*
399 Cover a protocol design error: error packet does not
400 contain the server status. Therefore, the client has no way
401 to find out whether there are more result sets of
402 a multiple-result-set statement pending. Luckily, in 5.0 an
403 error always aborts execution of a statement, wherever it is
404 a multi-statement or a stored procedure, so it should be
405 safe to unconditionally turn off the flag here.
406 */
407 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & ~SERVER_MORE_RESULTS_EXISTS);
408 /*
409 This will copy the error code and the messages, as they
410 are buffers in the struct
411 */
412 COPY_CLIENT_ERROR(conn->error_info, rset_header.error_info);
413 ret = FAIL;
414 DBG_ERR_FMT("error=%s", rset_header.error_info.error);
415 /* Return back from CONN_QUERY_SENT */
416 SET_CONNECTION_STATE(&conn->state, CONN_READY);
417 break;
418 }
419 conn->error_info->error_no = 0;
420
421 switch (rset_header.field_count) {
422 case MYSQLND_NULL_LENGTH: { /* LOAD DATA LOCAL INFILE */
423 zend_bool is_warning;
424 DBG_INF("LOAD DATA");
425 conn->last_query_type = QUERY_LOAD_LOCAL;
426 conn->field_count = 0; /* overwrite previous value, or the last value could be used and lead to bug#53503 */
427 SET_CONNECTION_STATE(&conn->state, CONN_SENDING_LOAD_DATA);
428 ret = mysqlnd_handle_local_infile(conn, rset_header.info_or_local_file.s, &is_warning);
429 SET_CONNECTION_STATE(&conn->state, (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT);
430 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
431 break;
432 }
433 case 0: /* UPSERT */
434 DBG_INF("UPSERT");
435 conn->last_query_type = QUERY_UPSERT;
436 conn->field_count = rset_header.field_count;
437 UPSERT_STATUS_RESET(conn->upsert_status);
438 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, rset_header.warning_count);
439 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, rset_header.server_status);
440 UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, rset_header.affected_rows);
441 UPSERT_STATUS_SET_LAST_INSERT_ID(conn->upsert_status, rset_header.last_insert_id);
442 SET_NEW_MESSAGE(conn->last_message.s, conn->last_message.l,
443 rset_header.info_or_local_file.s, rset_header.info_or_local_file.l);
444 /* Result set can follow UPSERT statement, check server_status */
445 if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
446 SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
447 } else {
448 SET_CONNECTION_STATE(&conn->state, CONN_READY);
449 }
450 ret = PASS;
451 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
452 break;
453 default: do { /* Result set */
454 MYSQLND_RES * result;
455 enum_mysqlnd_collected_stats statistic = STAT_LAST;
456
457 DBG_INF("Result set pending");
458 SET_EMPTY_MESSAGE(conn->last_message.s, conn->last_message.l);
459
460 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_RSET_QUERY);
461 UPSERT_STATUS_RESET(conn->upsert_status);
462 /* restore after zeroing */
463 UPSERT_STATUS_SET_AFFECTED_ROWS_TO_ERROR(conn->upsert_status);
464
465 conn->last_query_type = QUERY_SELECT;
466 SET_CONNECTION_STATE(&conn->state, CONN_FETCHING_DATA);
467 /* PS has already allocated it */
468 conn->field_count = rset_header.field_count;
469 if (!stmt) {
470 result = conn->current_result = conn->m->result_init(rset_header.field_count);
471 } else {
472 if (!stmt->result) {
473 DBG_INF("This is 'SHOW'/'EXPLAIN'-like query.");
474 /*
475 This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
476 prepared statements can't send result set metadata for these queries
477 on prepare stage. Read it now.
478 */
479 result = stmt->result = conn->m->result_init(rset_header.field_count);
480 } else {
481 /*
482 Update result set metadata if it for some reason changed between
483 prepare and execute, i.e.:
484 - in case of 'SELECT ?' we don't know column type unless data was
485 supplied to mysql_stmt_execute, so updated column type is sent
486 now.
487 - if data dictionary changed between prepare and execute, for
488 example a table used in the query was altered.
489 Note, that now (4.1.3) we always send metadata in reply to
490 COM_STMT_EXECUTE (even if it is not necessary), so either this or
491 previous branch always works.
492 */
493 }
494 result = stmt->result;
495 }
496 if (!result) {
497 SET_OOM_ERROR(conn->error_info);
498 ret = FAIL;
499 break;
500 }
501
502 if (FAIL == (ret = result->m.read_result_metadata(result, conn))) {
503 /* For PS, we leave them in Prepared state */
504 if (!stmt && conn->current_result) {
505 mnd_efree(conn->current_result);
506 conn->current_result = NULL;
507 }
508 DBG_ERR("Error occurred while reading metadata");
509 break;
510 }
511
512 /* Check for SERVER_STATUS_MORE_RESULTS if needed */
513 conn->payload_decoder_factory->m.init_eof_packet(&fields_eof);
514 if (FAIL == (ret = PACKET_READ(conn, &fields_eof))) {
515 DBG_ERR("Error occurred while reading the EOF packet");
516 result->m.free_result_contents(result);
517 mysqlnd_mempool_destroy(result->memory_pool);
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)(MYSQLND_RES_BUFFERED * const result)
572 {
573 const MYSQLND_RES_BUFFERED_ZVAL * set = (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)(MYSQLND_RES_BUFFERED * const result)
604 {
605 const MYSQLND_RES_BUFFERED_C * set = (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)(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)(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 SET_CONNECTION_STATE(&conn->state, CONN_READY);
745 result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
746 } else if (row_packet->eof) {
747 /* Mark the connection as usable again */
748 DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
749 result->unbuf->eof_reached = TRUE;
750
751 UPSERT_STATUS_RESET(conn->upsert_status);
752 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
753 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
754 /*
755 result->row_packet will be cleaned when
756 destroying the result object
757 */
758 if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
759 SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
760 } else {
761 SET_CONNECTION_STATE(&conn->state, CONN_READY);
762 }
763 result->unbuf->m.free_last_data(result->unbuf, conn->stats);
764 }
765
766 mysqlnd_mempool_restore_state(result->memory_pool);
767 result->memory_pool->checkpoint = checkpoint;
768
769 DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
770 DBG_RETURN(PASS);
771 }
772 /* }}} */
773
774
775 /* {{{ mysqlnd_result_unbuffered::fetch_row */
776 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_unbuffered,fetch_row)777 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything)
778 {
779 enum_func_status ret;
780 zval *row = (zval *) param;
781 MYSQLND_PACKET_ROW *row_packet = result->unbuf->row_packet;
782 const MYSQLND_RES_METADATA * const meta = result->meta;
783 MYSQLND_CONN_DATA * const conn = result->conn;
784 void *checkpoint;
785
786 DBG_ENTER("mysqlnd_result_unbuffered::fetch_row");
787
788 *fetched_anything = FALSE;
789 if (result->unbuf->eof_reached) {
790 /* No more rows obviously */
791 DBG_RETURN(PASS);
792 }
793 if (GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA) {
794 SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
795 DBG_RETURN(FAIL);
796 }
797 if (!row_packet) {
798 /* Not fully initialized object that is being cleaned up */
799 DBG_RETURN(FAIL);
800 }
801 /* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
802 row_packet->skip_extraction = row? FALSE:TRUE;
803
804 checkpoint = result->memory_pool->checkpoint;
805 mysqlnd_mempool_save_state(result->memory_pool);
806
807 /*
808 If we skip rows (row == NULL) we have to
809 result->m.unbuffered_free_last_data() before it. The function returns always true.
810 */
811 if (PASS == (ret = PACKET_READ(conn, row_packet)) && !row_packet->eof) {
812 result->unbuf->m.free_last_data(result->unbuf, conn->stats);
813
814 result->unbuf->last_row_data = row_packet->fields;
815 result->unbuf->last_row_buffer = row_packet->row_buffer;
816 row_packet->fields = NULL;
817 row_packet->row_buffer.ptr = NULL;
818
819 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
820
821 if (!row_packet->skip_extraction) {
822 unsigned int i, field_count = meta->field_count;
823
824 enum_func_status rc = result->unbuf->m.row_decoder(&result->unbuf->last_row_buffer,
825 result->unbuf->last_row_data,
826 field_count,
827 row_packet->fields_metadata,
828 conn->options->int_and_float_native,
829 conn->stats);
830 if (PASS != rc) {
831 mysqlnd_mempool_restore_state(result->memory_pool);
832 result->memory_pool->checkpoint = checkpoint;
833 DBG_RETURN(FAIL);
834 }
835 {
836 HashTable * row_ht = Z_ARRVAL_P(row);
837 MYSQLND_FIELD * field = meta->fields;
838 size_t * lengths = result->unbuf->lengths;
839
840 for (i = 0; i < field_count; i++, field++) {
841 zval * data = &result->unbuf->last_row_data[i];
842 const size_t len = (Z_TYPE_P(data) == IS_STRING)? Z_STRLEN_P(data) : 0;
843
844 if (flags & MYSQLND_FETCH_NUM) {
845 if (zend_hash_index_add(row_ht, i, data) != NULL) {
846 Z_TRY_ADDREF_P(data);
847 }
848 }
849 if (flags & MYSQLND_FETCH_ASSOC) {
850 /* zend_hash_quick_update needs length + trailing zero */
851 /* QQ: Error handling ? */
852 /*
853 zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
854 the index is a numeric and convert it to it. This however means constant
855 hashing of the column name, which is not needed as it can be precomputed.
856 */
857 Z_TRY_ADDREF_P(data);
858 if (meta->fields[i].is_numeric == FALSE) {
859 zend_hash_update(row_ht, meta->fields[i].sname, data);
860 } else {
861 zend_hash_index_update(row_ht, meta->fields[i].num_key, data);
862 }
863 }
864
865 if (lengths) {
866 lengths[i] = len;
867 }
868
869 if (field->max_length < len) {
870 field->max_length = len;
871 }
872 }
873 }
874 }
875 result->unbuf->row_count++;
876 *fetched_anything = TRUE;
877 } else if (ret == FAIL) {
878 if (row_packet->error_info.error_no) {
879 COPY_CLIENT_ERROR(conn->error_info, row_packet->error_info);
880 DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
881 }
882 SET_CONNECTION_STATE(&conn->state, CONN_READY);
883 result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
884 } else if (row_packet->eof) {
885 /* Mark the connection as usable again */
886 DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
887 result->unbuf->eof_reached = TRUE;
888
889 UPSERT_STATUS_RESET(conn->upsert_status);
890 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet->warning_count);
891 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet->server_status);
892 /*
893 result->row_packet will be cleaned when
894 destroying the result object
895 */
896 if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
897 SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
898 } else {
899 SET_CONNECTION_STATE(&conn->state, CONN_READY);
900 }
901 result->unbuf->m.free_last_data(result->unbuf, conn->stats);
902 }
903
904 mysqlnd_mempool_restore_state(result->memory_pool);
905 result->memory_pool->checkpoint = checkpoint;
906
907 DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
908 DBG_RETURN(PASS);
909 }
910 /* }}} */
911
912
913 /* {{{ mysqlnd_res::use_result */
914 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,use_result)915 MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, const zend_bool ps)
916 {
917 MYSQLND_CONN_DATA * const conn = result->conn;
918 DBG_ENTER("mysqlnd_res::use_result");
919
920 SET_EMPTY_ERROR(conn->error_info);
921
922 if (ps == FALSE) {
923 result->type = MYSQLND_RES_NORMAL;
924 } else {
925 result->type = MYSQLND_RES_PS_UNBUF;
926 }
927
928 result->unbuf = mysqlnd_result_unbuffered_init(result, result->field_count, ps);
929 if (!result->unbuf) {
930 goto oom;
931 }
932
933 /*
934 Will be freed in the mysqlnd_internal_free_result_contents() called
935 by the resource destructor. mysqlnd_result_unbuffered::fetch_row() expects
936 this to be not NULL.
937 */
938 /* FALSE = non-persistent */
939 {
940 struct st_mysqlnd_packet_row *row_packet = mnd_emalloc(sizeof(struct st_mysqlnd_packet_row));
941
942 conn->payload_decoder_factory->m.init_row_packet(row_packet);
943 row_packet->result_set_memory_pool = result->unbuf->result_set_memory_pool;
944 row_packet->field_count = result->field_count;
945 row_packet->binary_protocol = ps;
946 row_packet->fields_metadata = result->meta->fields;
947
948 result->unbuf->row_packet = row_packet;
949 }
950
951 DBG_RETURN(result);
952 oom:
953 SET_OOM_ERROR(conn->error_info);
954 DBG_RETURN(NULL);
955 }
956 /* }}} */
957
958
959 /* {{{ mysqlnd_result_buffered::fetch_row_c */
960 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered,fetch_row_c)961 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything)
962 {
963 enum_func_status ret = FAIL;
964 MYSQLND_ROW_C * row = (MYSQLND_ROW_C *) param;
965 const MYSQLND_RES_METADATA * const meta = result->meta;
966 unsigned int field_count = meta->field_count;
967 MYSQLND_CONN_DATA * const conn = result->conn;
968 DBG_ENTER("mysqlnd_result_buffered::fetch_row_c");
969
970 if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) {
971 MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
972
973 /* If we haven't read everything */
974 if (set->data_cursor &&
975 (set->data_cursor - set->data) < (result->stored_data->row_count * field_count))
976 {
977 zval *current_row = set->data_cursor;
978 unsigned int i;
979
980 if (Z_ISUNDEF(current_row[0])) {
981 uint64_t row_num = (set->data_cursor - set->data) / field_count;
982 enum_func_status rc = set->m.row_decoder(&set->row_buffers[row_num],
983 current_row,
984 field_count,
985 meta->fields,
986 conn->options->int_and_float_native,
987 conn->stats);
988 if (rc != PASS) {
989 DBG_RETURN(FAIL);
990 }
991 ++set->initialized_rows;
992 for (i = 0; i < field_count; ++i) {
993 /*
994 NULL fields are 0 length, 0 is not more than 0
995 String of zero size, definitely can't be the next max_length.
996 Thus for NULL and zero-length we are quite efficient.
997 */
998 if (Z_TYPE(current_row[i]) == IS_STRING) {
999 const size_t len = Z_STRLEN(current_row[i]);
1000 if (meta->fields[i].max_length < len) {
1001 meta->fields[i].max_length = len;
1002 }
1003 }
1004 }
1005 }
1006
1007 /* BEGIN difference between normal normal fetch and _c */
1008 /* there is no conn handle in this function thus we can't set OOM in error_info */
1009 *row = mnd_malloc(field_count * sizeof(char *));
1010 if (*row) {
1011 for (i = 0; i < field_count; ++i) {
1012 zval * data = ¤t_row[i];
1013
1014 set->lengths[i] = (Z_TYPE_P(data) == IS_STRING)? Z_STRLEN_P(data) : 0;
1015
1016 if (Z_TYPE_P(data) != IS_NULL) {
1017 convert_to_string(data);
1018 (*row)[i] = Z_STRVAL_P(data);
1019 } else {
1020 (*row)[i] = NULL;
1021 }
1022 }
1023 set->data_cursor += field_count;
1024 MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
1025 } else {
1026 SET_OOM_ERROR(conn->error_info);
1027 }
1028 /* END difference between normal normal fetch and _c */
1029
1030 *fetched_anything = *row? TRUE:FALSE;
1031 ret = *row? PASS:FAIL;
1032 } else {
1033 set->data_cursor = NULL;
1034 DBG_INF("EOF reached");
1035 *fetched_anything = FALSE;
1036 ret = PASS;
1037 }
1038 } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) {
1039 /*
1040 We don't support _C with pdo because it uses the data in a different way - just references it.
1041 We will either leak or give nirvana pointers
1042 */
1043 *fetched_anything = FALSE;
1044 DBG_RETURN(FAIL);
1045 }
1046 DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
1047 DBG_RETURN(ret);
1048 }
1049 /* }}} */
1050
1051
1052 /* {{{ mysqlnd_result_buffered_zval::fetch_row */
1053 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_zval,fetch_row)1054 MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything)
1055 {
1056 enum_func_status ret = FAIL;
1057 zval * row = (zval *) param;
1058 const MYSQLND_RES_METADATA * const meta = result->meta;
1059 const unsigned int field_count = meta->field_count;
1060 MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
1061 MYSQLND_CONN_DATA * const conn = result->conn;
1062
1063 DBG_ENTER("mysqlnd_result_buffered_zval::fetch_row");
1064
1065 /* If we haven't read everything */
1066 if (set->data_cursor && (set->data_cursor - set->data) < (set->row_count * field_count)) {
1067 unsigned int i;
1068 zval *current_row = set->data_cursor;
1069
1070 if (Z_ISUNDEF(current_row[0])) {
1071 const size_t row_num = (set->data_cursor - set->data) / field_count;
1072 enum_func_status rc = set->m.row_decoder(&set->row_buffers[row_num],
1073 current_row,
1074 field_count,
1075 meta->fields,
1076 conn->options->int_and_float_native,
1077 conn->stats);
1078 if (rc != PASS) {
1079 DBG_RETURN(FAIL);
1080 }
1081 ++set->initialized_rows;
1082 for (i = 0; i < field_count; ++i) {
1083 /*
1084 NULL fields are 0 length, 0 is not more than 0
1085 String of zero size, definitely can't be the next max_length.
1086 Thus for NULL and zero-length we are quite efficient.
1087 */
1088 if (Z_TYPE(current_row[i]) == IS_STRING) {
1089 const size_t len = Z_STRLEN(current_row[i]);
1090 if (meta->fields[i].max_length < len) {
1091 meta->fields[i].max_length = len;
1092 }
1093 }
1094 }
1095 }
1096
1097 for (i = 0; i < field_count; ++i) {
1098 zval * data = ¤t_row[i];
1099
1100 set->lengths[i] = (Z_TYPE_P(data) == IS_STRING)? Z_STRLEN_P(data) : 0;
1101
1102 if (flags & MYSQLND_FETCH_NUM) {
1103 if (zend_hash_index_add(Z_ARRVAL_P(row), i, data) != NULL) {
1104 Z_TRY_ADDREF_P(data);
1105 }
1106 }
1107 if (flags & MYSQLND_FETCH_ASSOC) {
1108 /* zend_hash_quick_update needs length + trailing zero */
1109 /* QQ: Error handling ? */
1110 /*
1111 zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1112 the index is a numeric and convert it to it. This however means constant
1113 hashing of the column name, which is not needed as it can be precomputed.
1114 */
1115 Z_TRY_ADDREF_P(data);
1116 if (meta->fields[i].is_numeric == FALSE) {
1117 zend_hash_update(Z_ARRVAL_P(row), meta->fields[i].sname, data);
1118 } else {
1119 zend_hash_index_update(Z_ARRVAL_P(row), meta->fields[i].num_key, data);
1120 }
1121 }
1122 }
1123 set->data_cursor += field_count;
1124 MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
1125 *fetched_anything = TRUE;
1126 ret = PASS;
1127 } else {
1128 set->data_cursor = NULL;
1129 DBG_INF("EOF reached");
1130 *fetched_anything = FALSE;
1131 ret = PASS;
1132 }
1133 DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
1134 DBG_RETURN(ret);
1135 }
1136 /* }}} */
1137
1138
1139 /* {{{ mysqlnd_result_buffered_c::fetch_row */
1140 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_c,fetch_row)1141 MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything)
1142 {
1143 enum_func_status ret = FAIL;
1144 zval * row = (zval *) param;
1145 const MYSQLND_RES_METADATA * const meta = result->meta;
1146 const unsigned int field_count = meta->field_count;
1147 MYSQLND_CONN_DATA * const conn = result->conn;
1148
1149 MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result->stored_data;
1150
1151 DBG_ENTER("mysqlnd_result_buffered_c::fetch_row");
1152
1153 /* If we haven't read everything */
1154 if (set->current_row < set->row_count) {
1155 enum_func_status rc;
1156 zval * current_row;
1157 unsigned int i;
1158
1159 current_row = mnd_emalloc(field_count * sizeof(zval));
1160 if (!current_row) {
1161 SET_OOM_ERROR(conn->error_info);
1162 DBG_RETURN(FAIL);
1163 }
1164
1165 rc = result->stored_data->m.row_decoder(&result->stored_data->row_buffers[set->current_row],
1166 current_row,
1167 field_count,
1168 meta->fields,
1169 conn->options->int_and_float_native,
1170 conn->stats);
1171 if (rc != PASS) {
1172 DBG_RETURN(FAIL);
1173 }
1174 if (!ZEND_BIT_TEST(set->initialized, set->current_row)) {
1175 set->initialized[set->current_row >> 3] |= (1 << (set->current_row & 7)); /* mark initialized */
1176
1177 ++set->initialized_rows;
1178
1179 for (i = 0; i < field_count; ++i) {
1180 /*
1181 NULL fields are 0 length, 0 is not more than 0
1182 String of zero size, definitely can't be the next max_length.
1183 Thus for NULL and zero-length we are quite efficient.
1184 */
1185 if (Z_TYPE(current_row[i]) == IS_STRING) {
1186 const size_t len = Z_STRLEN(current_row[i]);
1187 if (meta->fields[i].max_length < len) {
1188 meta->fields[i].max_length = len;
1189 }
1190 }
1191 }
1192 }
1193
1194 for (i = 0; i < field_count; ++i) {
1195 zval * data = ¤t_row[i];
1196
1197 set->lengths[i] = (Z_TYPE_P(data) == IS_STRING)? Z_STRLEN_P(data) : 0;
1198
1199 if (flags & MYSQLND_FETCH_NUM) {
1200 if (zend_hash_index_add(Z_ARRVAL_P(row), i, data)) {
1201 Z_TRY_ADDREF_P(data);
1202 }
1203 }
1204 if (flags & MYSQLND_FETCH_ASSOC) {
1205 /* zend_hash_quick_update needs length + trailing zero */
1206 /* QQ: Error handling ? */
1207 /*
1208 zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1209 the index is a numeric and convert it to it. This however means constant
1210 hashing of the column name, which is not needed as it can be precomputed.
1211 */
1212 Z_TRY_ADDREF_P(data);
1213 if (meta->fields[i].is_numeric == FALSE) {
1214 zend_hash_update(Z_ARRVAL_P(row), meta->fields[i].sname, data);
1215 } else {
1216 zend_hash_index_update(Z_ARRVAL_P(row), meta->fields[i].num_key, data);
1217 }
1218 }
1219 /*
1220 This will usually not destroy anything but decref.
1221 However, if neither NUM nor ASSOC is set we will free memory cleanly and won't leak.
1222 It also simplifies the handling of Z_ADDREF_P because we don't need to check if only
1223 either NUM or ASSOC is set but not both.
1224 */
1225 zval_ptr_dtor_nogc(data);
1226 }
1227 mnd_efree(current_row);
1228 ++set->current_row;
1229 MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
1230 *fetched_anything = TRUE;
1231 ret = PASS;
1232 } else {
1233 if (set->current_row == set->row_count) {
1234 set->current_row = set->row_count + 1;
1235 }
1236 DBG_INF_FMT("EOF reached. current_row=%llu", (unsigned long long) set->current_row);
1237 *fetched_anything = FALSE;
1238 ret = PASS;
1239 }
1240
1241 DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
1242 DBG_RETURN(ret);
1243 }
1244 /* }}} */
1245
1246
1247 /* {{{ mysqlnd_res::fetch_row */
1248 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,fetch_row)1249 MYSQLND_METHOD(mysqlnd_res, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool *fetched_anything)
1250 {
1251 const mysqlnd_fetch_row_func f = result->stored_data? result->stored_data->m.fetch_row:(result->unbuf? result->unbuf->m.fetch_row:NULL);
1252 if (f) {
1253 return f(result, param, flags, fetched_anything);
1254 }
1255 *fetched_anything = FALSE;
1256 return PASS;
1257 }
1258 /* }}} */
1259
1260
1261 /* {{{ mysqlnd_res::store_result_fetch_data */
1262 enum_func_status
MYSQLND_METHOD(mysqlnd_res,store_result_fetch_data)1263 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result,
1264 MYSQLND_RES_METADATA * meta,
1265 MYSQLND_ROW_BUFFER **row_buffers,
1266 zend_bool binary_protocol)
1267 {
1268 enum_func_status ret;
1269 uint64_t total_allocated_rows = 0;
1270 unsigned int free_rows = 0;
1271 MYSQLND_RES_BUFFERED * set = result->stored_data;
1272 MYSQLND_PACKET_ROW row_packet;
1273
1274 DBG_ENTER("mysqlnd_res::store_result_fetch_data");
1275 if (!set || !row_buffers) {
1276 ret = FAIL;
1277 goto end;
1278 }
1279
1280 *row_buffers = NULL;
1281
1282 conn->payload_decoder_factory->m.init_row_packet(&row_packet);
1283 set->references = 1;
1284
1285 row_packet.result_set_memory_pool = result->stored_data->result_set_memory_pool;
1286 row_packet.field_count = meta->field_count;
1287 row_packet.binary_protocol = binary_protocol;
1288 row_packet.fields_metadata = meta->fields;
1289
1290 row_packet.skip_extraction = TRUE; /* let php_mysqlnd_rowp_read() not allocate row_packet.fields, we will do it */
1291
1292 while (FAIL != (ret = PACKET_READ(conn, &row_packet)) && !row_packet.eof) {
1293 if (!free_rows) {
1294 MYSQLND_ROW_BUFFER * new_row_buffers;
1295
1296 if (total_allocated_rows < 1024) {
1297 if (total_allocated_rows == 0) {
1298 free_rows = 1;
1299 total_allocated_rows = 1;
1300 } else {
1301 free_rows = total_allocated_rows;
1302 total_allocated_rows *= 2;
1303 }
1304 } else {
1305 free_rows = 1024;
1306 total_allocated_rows += 1024;
1307 }
1308
1309 /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1310 if (total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER) > SIZE_MAX) {
1311 SET_OOM_ERROR(conn->error_info);
1312 ret = FAIL;
1313 goto free_end;
1314 }
1315 if (*row_buffers) {
1316 new_row_buffers = mnd_erealloc(*row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER)));
1317 } else {
1318 new_row_buffers = mnd_emalloc((size_t)(total_allocated_rows * sizeof(MYSQLND_ROW_BUFFER)));
1319 }
1320 if (!new_row_buffers) {
1321 SET_OOM_ERROR(conn->error_info);
1322 ret = FAIL;
1323 goto free_end;
1324 }
1325 *row_buffers = new_row_buffers;
1326 }
1327 free_rows--;
1328 (*row_buffers)[set->row_count] = row_packet.row_buffer;
1329
1330 set->row_count++;
1331
1332 /* So row_packet's destructor function won't efree() it */
1333 row_packet.fields = NULL;
1334 row_packet.row_buffer.ptr = NULL;
1335
1336 /*
1337 No need to FREE_ALLOCA as we can reuse the
1338 'lengths' and 'fields' arrays. For lengths its absolutely safe.
1339 'fields' is reused because the ownership of the strings has been
1340 transferred above.
1341 */
1342 }
1343 /* Overflow ? */
1344 MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats,
1345 binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
1346 STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
1347 set->row_count);
1348
1349 /* Finally clean */
1350 if (row_packet.eof) {
1351 UPSERT_STATUS_RESET(conn->upsert_status);
1352 UPSERT_STATUS_SET_WARNINGS(conn->upsert_status, row_packet.warning_count);
1353 UPSERT_STATUS_SET_SERVER_STATUS(conn->upsert_status, row_packet.server_status);
1354 }
1355
1356 /* save some memory */
1357 if (free_rows) {
1358 /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1359 if (set->row_count * sizeof(MYSQLND_ROW_BUFFER) > SIZE_MAX) {
1360 SET_OOM_ERROR(conn->error_info);
1361 ret = FAIL;
1362 goto free_end;
1363 }
1364 *row_buffers = mnd_erealloc(*row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_ROW_BUFFER)));
1365 }
1366
1367 if (UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status) & SERVER_MORE_RESULTS_EXISTS) {
1368 SET_CONNECTION_STATE(&conn->state, CONN_NEXT_RESULT_PENDING);
1369 } else {
1370 SET_CONNECTION_STATE(&conn->state, CONN_READY);
1371 }
1372
1373 if (ret == FAIL) {
1374 COPY_CLIENT_ERROR(&set->error_info, row_packet.error_info);
1375 } else {
1376 /* libmysql's documentation says it should be so for SELECT statements */
1377 UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, set->row_count);
1378 }
1379 DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u",
1380 ret == PASS? "PASS":"FAIL",
1381 (uint32_t) set->row_count,
1382 UPSERT_STATUS_GET_WARNINGS(conn->upsert_status),
1383 UPSERT_STATUS_GET_SERVER_STATUS(conn->upsert_status));
1384 free_end:
1385 PACKET_FREE(&row_packet);
1386 end:
1387 DBG_INF_FMT("rows=%llu", (unsigned long long)result->stored_data->row_count);
1388 DBG_RETURN(ret);
1389 }
1390 /* }}} */
1391
1392
1393 /* {{{ mysqlnd_res::store_result */
1394 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,store_result)1395 MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
1396 MYSQLND_CONN_DATA * const conn,
1397 const unsigned int flags)
1398 {
1399 enum_func_status ret;
1400 MYSQLND_ROW_BUFFER **row_buffers = NULL;
1401
1402 DBG_ENTER("mysqlnd_res::store_result");
1403
1404 /* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
1405 /* In case of error the reference will be released in free_result_internal() called indirectly by our caller */
1406 result->conn = conn->m->get_reference(conn);
1407 result->type = MYSQLND_RES_NORMAL;
1408
1409 SET_CONNECTION_STATE(&conn->state, CONN_FETCHING_DATA);
1410
1411 if (flags & MYSQLND_STORE_NO_COPY) {
1412 result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_zval_init(result, result->field_count, flags & MYSQLND_STORE_PS);
1413 if (!result->stored_data) {
1414 SET_OOM_ERROR(conn->error_info);
1415 DBG_RETURN(NULL);
1416 }
1417 row_buffers = &result->stored_data->row_buffers;
1418 } else if (flags & MYSQLND_STORE_COPY) {
1419 result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_c_init(result, result->field_count, flags & MYSQLND_STORE_PS);
1420 if (!result->stored_data) {
1421 SET_OOM_ERROR(conn->error_info);
1422 DBG_RETURN(NULL);
1423 }
1424 row_buffers = &result->stored_data->row_buffers;
1425 }
1426 ret = result->m.store_result_fetch_data(conn, result, result->meta, row_buffers, flags & MYSQLND_STORE_PS);
1427
1428 if (FAIL == ret) {
1429 if (result->stored_data) {
1430 COPY_CLIENT_ERROR(conn->error_info, result->stored_data->error_info);
1431 } else {
1432 SET_OOM_ERROR(conn->error_info);
1433 }
1434 DBG_RETURN(NULL);
1435 } else {
1436 if (flags & MYSQLND_STORE_NO_COPY) {
1437 const MYSQLND_RES_METADATA * const meta = result->meta;
1438 MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data;
1439
1440 if (set->row_count) {
1441 /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1442 if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) {
1443 SET_OOM_ERROR(conn->error_info);
1444 DBG_RETURN(NULL);
1445 }
1446 /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */
1447 set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval)));
1448 if (!set->data) {
1449 SET_OOM_ERROR(conn->error_info);
1450 DBG_RETURN(NULL);
1451 }
1452 memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval)));
1453 }
1454 /* Position at the first row */
1455 set->data_cursor = set->data;
1456 } else if (flags & MYSQLND_STORE_COPY) {
1457 MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result->stored_data;
1458 set->current_row = 0;
1459 set->initialized = mnd_ecalloc((unsigned int) ((set->row_count / 8) + 1), sizeof(zend_uchar)); /* +1 for safety */
1460 }
1461 }
1462
1463 /* libmysql's documentation says it should be so for SELECT statements */
1464 UPSERT_STATUS_SET_AFFECTED_ROWS(conn->upsert_status, result->stored_data->row_count);
1465
1466 DBG_RETURN(result);
1467 }
1468 /* }}} */
1469
1470
1471 /* {{{ mysqlnd_res::skip_result */
1472 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,skip_result)1473 MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result)
1474 {
1475 zend_bool fetched_anything;
1476
1477 DBG_ENTER("mysqlnd_res::skip_result");
1478 /*
1479 Unbuffered sets
1480 A PS could be prepared - there is metadata and thus a stmt->result but the
1481 fetch_row function isn't actually set (NULL), thus we have to skip these.
1482 */
1483 if (result->unbuf && !result->unbuf->eof_reached) {
1484 MYSQLND_CONN_DATA * const conn = result->conn;
1485 DBG_INF("skipping result");
1486 /* We have to fetch all data to clean the line */
1487 MYSQLND_INC_CONN_STATISTIC(conn->stats,
1488 result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS:
1489 STAT_FLUSHED_PS_SETS);
1490
1491 while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything)) && fetched_anything == TRUE) {
1492 /* do nothing */;
1493 }
1494 }
1495 DBG_RETURN(PASS);
1496 }
1497 /* }}} */
1498
1499
1500 /* {{{ mysqlnd_res::free_result */
1501 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,free_result)1502 MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, const zend_bool implicit)
1503 {
1504 DBG_ENTER("mysqlnd_res::free_result");
1505
1506 MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL,
1507 implicit == TRUE? STAT_FREE_RESULT_IMPLICIT:
1508 STAT_FREE_RESULT_EXPLICIT);
1509
1510 result->m.free_result_internal(result);
1511 DBG_RETURN(PASS);
1512 }
1513 /* }}} */
1514
1515
1516 /* {{{ mysqlnd_res::data_seek */
1517 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,data_seek)1518 MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * const result, const uint64_t row)
1519 {
1520 DBG_ENTER("mysqlnd_res::data_seek");
1521 DBG_INF_FMT("row=%lu", row);
1522
1523 DBG_RETURN(result->stored_data? result->stored_data->m.data_seek(result->stored_data, row) : FAIL);
1524 }
1525 /* }}} */
1526
1527
1528 /* {{{ mysqlnd_result_buffered_zval::data_seek */
1529 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_zval,data_seek)1530 MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row)
1531 {
1532 MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result;
1533 DBG_ENTER("mysqlnd_result_buffered_zval::data_seek");
1534
1535 /* libmysql just moves to the end, it does traversing of a linked list */
1536 if (row >= set->row_count) {
1537 set->data_cursor = NULL;
1538 } else {
1539 set->data_cursor = set->data + row * result->field_count;
1540 }
1541 DBG_RETURN(PASS);
1542 }
1543 /* }}} */
1544
1545
1546 /* {{{ mysqlnd_result_buffered_c::data_seek */
1547 static enum_func_status
MYSQLND_METHOD(mysqlnd_result_buffered_c,data_seek)1548 MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row)
1549 {
1550 MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result;
1551 DBG_ENTER("mysqlnd_result_buffered_c::data_seek");
1552
1553 /* libmysql just moves to the end, it does traversing of a linked list */
1554 if (row >= set->row_count) {
1555 set->current_row = set->row_count;
1556 } else {
1557 set->current_row = row;
1558 }
1559 DBG_RETURN(PASS);
1560 }
1561 /* }}} */
1562
1563
1564 /* {{{ mysqlnd_result_unbuffered::num_rows */
1565 static uint64_t
MYSQLND_METHOD(mysqlnd_result_unbuffered,num_rows)1566 MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows)(const MYSQLND_RES_UNBUFFERED * const result)
1567 {
1568 /* Be compatible with libmysql. We count row_count, but will return 0 */
1569 return result->eof_reached? result->row_count : 0;
1570 }
1571 /* }}} */
1572
1573
1574 /* {{{ mysqlnd_result_buffered::num_rows */
1575 static uint64_t
MYSQLND_METHOD(mysqlnd_result_buffered,num_rows)1576 MYSQLND_METHOD(mysqlnd_result_buffered, num_rows)(const MYSQLND_RES_BUFFERED * const result)
1577 {
1578 return result->row_count;
1579 }
1580 /* }}} */
1581
1582
1583 /* {{{ mysqlnd_res::num_rows */
1584 static uint64_t
MYSQLND_METHOD(mysqlnd_res,num_rows)1585 MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result)
1586 {
1587 return result->stored_data?
1588 result->stored_data->m.num_rows(result->stored_data) :
1589 (result->unbuf? result->unbuf->m.num_rows(result->unbuf) : 0);
1590 }
1591 /* }}} */
1592
1593
1594 /* {{{ mysqlnd_res::num_fields */
1595 static unsigned int
MYSQLND_METHOD(mysqlnd_res,num_fields)1596 MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result)
1597 {
1598 return result->field_count;
1599 }
1600 /* }}} */
1601
1602
1603 /* {{{ mysqlnd_res::fetch_field */
1604 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field)1605 MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result)
1606 {
1607 DBG_ENTER("mysqlnd_res::fetch_field");
1608 do {
1609 if (result->meta) {
1610 /*
1611 We optimize the result set, so we don't convert all the data from raw buffer format to
1612 zval arrays during store. In the case someone doesn't read all the lines this will
1613 save time. However, when a metadata call is done, we need to calculate max_length.
1614 We don't have control whether max_length will be used, unfortunately. Otherwise we
1615 could have been able to skip that step.
1616 Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1617 then we can have max_length as dynamic property, which will be calculated during runtime and
1618 not during mysqli_fetch_field() time.
1619 */
1620 if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1621 const MYSQLND_CONN_DATA * const conn = result->conn;
1622 DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1623 /* we have to initialize the rest to get the updated max length */
1624 if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data,
1625 result->meta,
1626 conn->stats,
1627 conn->options->int_and_float_native))
1628 {
1629 break;
1630 }
1631 }
1632 DBG_RETURN(result->meta->m->fetch_field(result->meta));
1633 }
1634 } while (0);
1635 DBG_RETURN(NULL);
1636 }
1637 /* }}} */
1638
1639
1640 /* {{{ mysqlnd_res::fetch_field_direct */
1641 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field_direct)1642 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET fieldnr)
1643 {
1644 DBG_ENTER("mysqlnd_res::fetch_field_direct");
1645 do {
1646 if (result->meta) {
1647 /*
1648 We optimize the result set, so we don't convert all the data from raw buffer format to
1649 zval arrays during store. In the case someone doesn't read all the lines this will
1650 save time. However, when a metadata call is done, we need to calculate max_length.
1651 We don't have control whether max_length will be used, unfortunately. Otherwise we
1652 could have been able to skip that step.
1653 Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1654 then we can have max_length as dynamic property, which will be calculated during runtime and
1655 not during mysqli_fetch_field_direct() time.
1656 */
1657 if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1658 const MYSQLND_CONN_DATA * const conn = result->conn;
1659 DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1660 /* we have to initialized the rest to get the updated max length */
1661 if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data,
1662 result->meta,
1663 conn->stats,
1664 conn->options->int_and_float_native))
1665 {
1666 break;
1667 }
1668 }
1669 DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr));
1670 }
1671 } while (0);
1672
1673 DBG_RETURN(NULL);
1674 }
1675 /* }}} */
1676
1677
1678 /* {{{ mysqlnd_res::fetch_field */
1679 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_fields)1680 MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result)
1681 {
1682 DBG_ENTER("mysqlnd_res::fetch_fields");
1683 do {
1684 if (result->meta) {
1685 if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1686 const MYSQLND_CONN_DATA * const conn = result->conn;
1687 /* we have to initialize the rest to get the updated max length */
1688 if (PASS != result->stored_data->m.initialize_result_set_rest(result->stored_data,
1689 result->meta,
1690 conn->stats,
1691 conn->options->int_and_float_native))
1692 {
1693 break;
1694 }
1695 }
1696 DBG_RETURN(result->meta->m->fetch_fields(result->meta));
1697 }
1698 } while (0);
1699 DBG_RETURN(NULL);
1700 }
1701 /* }}} */
1702
1703
1704 /* {{{ mysqlnd_res::field_seek */
1705 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_seek)1706 MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, const MYSQLND_FIELD_OFFSET field_offset)
1707 {
1708 return result->meta? result->meta->m->field_seek(result->meta, field_offset) : 0;
1709 }
1710 /* }}} */
1711
1712
1713 /* {{{ mysqlnd_res::field_tell */
1714 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_tell)1715 MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result)
1716 {
1717 return result->meta? result->meta->m->field_tell(result->meta) : 0;
1718 }
1719 /* }}} */
1720
1721
1722 /* {{{ mysqlnd_res::fetch_into */
1723 static void
MYSQLND_METHOD(mysqlnd_res,fetch_into)1724 MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, const unsigned int flags,
1725 zval *return_value,
1726 enum_mysqlnd_extension extension ZEND_FILE_LINE_DC)
1727 {
1728 zend_bool fetched_anything;
1729 unsigned int array_size;
1730
1731 DBG_ENTER("mysqlnd_res::fetch_into");
1732
1733 /*
1734 Hint Zend how many elements we will have in the hash. Thus it won't
1735 extend and rehash the hash constantly.
1736 */
1737 array_size = result->field_count;
1738 if ((flags & (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) == (MYSQLND_FETCH_NUM|MYSQLND_FETCH_ASSOC)) {
1739 array_size *= 2;
1740 }
1741 array_init_size(return_value, array_size);
1742 if (FAIL == result->m.fetch_row(result, (void *)return_value, flags, &fetched_anything)) {
1743 php_error_docref(NULL, E_WARNING, "Error while reading a row");
1744 zend_array_destroy(Z_ARR_P(return_value));
1745 RETVAL_FALSE;
1746 } else if (fetched_anything == FALSE) {
1747 zend_array_destroy(Z_ARR_P(return_value));
1748 switch (extension) {
1749 case MYSQLND_MYSQLI:
1750 RETVAL_NULL();
1751 break;
1752 case MYSQLND_MYSQL:
1753 RETVAL_FALSE;
1754 break;
1755 default:exit(0);
1756 }
1757 }
1758 /*
1759 return_value is IS_NULL for no more data and an array for data. Thus it's ok
1760 to return here.
1761 */
1762 DBG_VOID_RETURN;
1763 }
1764 /* }}} */
1765
1766
1767 /* {{{ mysqlnd_res::fetch_row_c */
1768 static MYSQLND_ROW_C
MYSQLND_METHOD(mysqlnd_res,fetch_row_c)1769 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result)
1770 {
1771 zend_bool fetched_anything;
1772 MYSQLND_ROW_C ret = NULL;
1773 DBG_ENTER("mysqlnd_res::fetch_row_c");
1774
1775 if (result->stored_data && result->stored_data->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row)) {
1776 MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything);
1777 } else if (result->unbuf && result->unbuf->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)) {
1778 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything);
1779 } else {
1780 ret = NULL;
1781 php_error_docref(NULL, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers");
1782 }
1783 DBG_RETURN(ret);
1784 }
1785 /* }}} */
1786
1787
1788 /* {{{ mysqlnd_res::fetch_all */
1789 static void
MYSQLND_METHOD(mysqlnd_res,fetch_all)1790 MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, const unsigned int flags, zval *return_value ZEND_FILE_LINE_DC)
1791 {
1792 zval row;
1793 zend_ulong i = 0;
1794 MYSQLND_RES_BUFFERED *set = result->stored_data;
1795
1796 DBG_ENTER("mysqlnd_res::fetch_all");
1797
1798 if ((!result->unbuf && !set)) {
1799 php_error_docref(NULL, E_WARNING, "fetch_all can be used only with buffered sets");
1800 if (result->conn) {
1801 SET_CLIENT_ERROR(result->conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "fetch_all can be used only with buffered sets");
1802 }
1803 RETVAL_NULL();
1804 DBG_VOID_RETURN;
1805 }
1806
1807 /* 4 is a magic value. The cast is safe, if larger then the array will be later extended - no big deal :) */
1808 array_init_size(return_value, set? (unsigned int) set->row_count : 4);
1809
1810 do {
1811 mysqlnd_fetch_into(result, flags, &row, MYSQLND_MYSQLI);
1812 if (Z_TYPE(row) != IS_ARRAY) {
1813 zval_ptr_dtor_nogc(&row);
1814 break;
1815 }
1816 add_index_zval(return_value, i++, &row);
1817 } while (1);
1818
1819 DBG_VOID_RETURN;
1820 }
1821 /* }}} */
1822
1823
1824 /* {{{ mysqlnd_res::fetch_field_data */
1825 static void
MYSQLND_METHOD(mysqlnd_res,fetch_field_data)1826 MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, unsigned int offset, zval *return_value)
1827 {
1828 zval row;
1829 zval *entry;
1830 unsigned int i = 0;
1831
1832 DBG_ENTER("mysqlnd_res::fetch_field_data");
1833 DBG_INF_FMT("offset=%u", offset);
1834 /*
1835 Hint Zend how many elements we will have in the hash. Thus it won't
1836 extend and rehash the hash constantly.
1837 */
1838 mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, &row, MYSQLND_MYSQL);
1839 if (Z_TYPE(row) != IS_ARRAY) {
1840 zval_ptr_dtor_nogc(&row);
1841 RETVAL_NULL();
1842 DBG_VOID_RETURN;
1843 }
1844
1845 zend_hash_internal_pointer_reset(Z_ARRVAL(row));
1846 while (i++ < offset) {
1847 zend_hash_move_forward(Z_ARRVAL(row));
1848 }
1849
1850 entry = zend_hash_get_current_data(Z_ARRVAL(row));
1851
1852 ZVAL_COPY(return_value, entry);
1853 zval_ptr_dtor_nogc(&row);
1854
1855 DBG_VOID_RETURN;
1856 }
1857 /* }}} */
1858
1859
1860 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1861 MYSQLND_METHOD(mysqlnd_res, fetch_row),
1862 MYSQLND_METHOD(mysqlnd_res, use_result),
1863 MYSQLND_METHOD(mysqlnd_res, store_result),
1864 MYSQLND_METHOD(mysqlnd_res, fetch_into),
1865 MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1866 MYSQLND_METHOD(mysqlnd_res, fetch_all),
1867 MYSQLND_METHOD(mysqlnd_res, fetch_field_data),
1868 MYSQLND_METHOD(mysqlnd_res, num_rows),
1869 MYSQLND_METHOD(mysqlnd_res, num_fields),
1870 MYSQLND_METHOD(mysqlnd_res, skip_result),
1871 MYSQLND_METHOD(mysqlnd_res, data_seek),
1872 MYSQLND_METHOD(mysqlnd_res, field_seek),
1873 MYSQLND_METHOD(mysqlnd_res, field_tell),
1874 MYSQLND_METHOD(mysqlnd_res, fetch_field),
1875 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1876 MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1877 MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1878 MYSQLND_METHOD(mysqlnd_res, fetch_lengths),
1879 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1880 MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1881 MYSQLND_METHOD(mysqlnd_res, free_result),
1882 MYSQLND_METHOD(mysqlnd_res, free_result_internal),
1883 MYSQLND_METHOD(mysqlnd_res, free_result_contents_internal),
1884 mysqlnd_result_meta_init
1885 MYSQLND_CLASS_METHODS_END;
1886
1887
1888 MYSQLND_CLASS_METHODS_START(mysqlnd_result_unbuffered)
1889 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row),
1890 NULL, /* row_decoder */
1891 MYSQLND_METHOD(mysqlnd_result_unbuffered, num_rows),
1892 MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_lengths),
1893 MYSQLND_METHOD(mysqlnd_result_unbuffered, free_last_data),
1894 MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)
1895 MYSQLND_CLASS_METHODS_END;
1896
1897
1898 MYSQLND_CLASS_METHODS_START(mysqlnd_result_buffered)
1899 NULL, /* fetch_row */
1900 NULL, /* row_decoder */
1901 MYSQLND_METHOD(mysqlnd_result_buffered, num_rows),
1902 NULL, /* fetch_lengths */
1903 NULL, /* data_seek */
1904 NULL, /* initialize_result_set_rest */
1905 MYSQLND_METHOD(mysqlnd_result_buffered, free_result)
1906 MYSQLND_CLASS_METHODS_END;
1907
1908
1909 /* {{{ mysqlnd_result_init */
1910 PHPAPI MYSQLND_RES *
mysqlnd_result_init(const unsigned int field_count)1911 mysqlnd_result_init(const unsigned int field_count)
1912 {
1913 const size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1914 MYSQLND_MEMORY_POOL * pool;
1915 MYSQLND_RES * ret;
1916
1917 DBG_ENTER("mysqlnd_result_init");
1918
1919 pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size));
1920 if (!pool) {
1921 DBG_RETURN(NULL);
1922 }
1923
1924 ret = pool->get_chunk(pool, alloc_size);
1925 memset(ret, 0, alloc_size);
1926
1927 ret->memory_pool = pool;
1928 ret->field_count = field_count;
1929 ret->m = *mysqlnd_result_get_methods();
1930
1931 mysqlnd_mempool_save_state(pool);
1932
1933 DBG_RETURN(ret);
1934 }
1935 /* }}} */
1936
1937
1938 /* {{{ mysqlnd_result_unbuffered_init */
1939 PHPAPI MYSQLND_RES_UNBUFFERED *
mysqlnd_result_unbuffered_init(MYSQLND_RES * result,const unsigned int field_count,const zend_bool ps)1940 mysqlnd_result_unbuffered_init(MYSQLND_RES *result, const unsigned int field_count, const zend_bool ps)
1941 {
1942 const size_t alloc_size = sizeof(MYSQLND_RES_UNBUFFERED) + mysqlnd_plugin_count() * sizeof(void *);
1943 MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1944 MYSQLND_RES_UNBUFFERED * ret;
1945
1946 DBG_ENTER("mysqlnd_result_unbuffered_init");
1947
1948 ret = pool->get_chunk(pool, alloc_size);
1949 memset(ret, 0, alloc_size);
1950
1951 ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1952 memset(ret->lengths, 0, field_count * sizeof(size_t));
1953
1954 ret->result_set_memory_pool = pool;
1955 ret->field_count= field_count;
1956 ret->ps = ps;
1957
1958 ret->m = *mysqlnd_result_unbuffered_get_methods();
1959
1960 if (ps) {
1961 ret->m.fetch_lengths = NULL; /* makes no sense */
1962 ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
1963 } else {
1964 ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol_zval;
1965 }
1966
1967 DBG_RETURN(ret);
1968 }
1969 /* }}} */
1970
1971
1972 /* {{{ mysqlnd_result_buffered_zval_init */
1973 PHPAPI MYSQLND_RES_BUFFERED_ZVAL *
mysqlnd_result_buffered_zval_init(MYSQLND_RES * result,const unsigned int field_count,const zend_bool ps)1974 mysqlnd_result_buffered_zval_init(MYSQLND_RES * result, const unsigned int field_count, const zend_bool ps)
1975 {
1976 const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_ZVAL) + mysqlnd_plugin_count() * sizeof(void *);
1977 MYSQLND_MEMORY_POOL * pool = result->memory_pool;
1978 MYSQLND_RES_BUFFERED_ZVAL * ret;
1979
1980 DBG_ENTER("mysqlnd_result_buffered_zval_init");
1981
1982 ret = pool->get_chunk(pool, alloc_size);
1983 memset(ret, 0, alloc_size);
1984
1985 if (FAIL == mysqlnd_error_info_init(&ret->error_info, 0)) {
1986 DBG_RETURN(NULL);
1987 }
1988
1989 ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
1990 memset(ret->lengths, 0, field_count * sizeof(size_t));
1991
1992 ret->result_set_memory_pool = pool;
1993 ret->field_count= field_count;
1994 ret->ps = ps;
1995 ret->m = *mysqlnd_result_buffered_get_methods();
1996 ret->type = MYSQLND_BUFFERED_TYPE_ZVAL;
1997
1998 if (ps) {
1999 ret->m.fetch_lengths = NULL; /* makes no sense */
2000 ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
2001 } else {
2002 ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol_zval;
2003 }
2004 ret->m.fetch_row = MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row);
2005 ret->m.fetch_lengths = MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_lengths);
2006 ret->m.data_seek = MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek);
2007 ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_zval, initialize_result_set_rest);
2008 DBG_RETURN(ret);
2009 }
2010 /* }}} */
2011
2012
2013 /* {{{ mysqlnd_result_buffered_c_init */
2014 PHPAPI MYSQLND_RES_BUFFERED_C *
mysqlnd_result_buffered_c_init(MYSQLND_RES * result,const unsigned int field_count,const zend_bool ps)2015 mysqlnd_result_buffered_c_init(MYSQLND_RES * result, const unsigned int field_count, const zend_bool ps)
2016 {
2017 const size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_C) + mysqlnd_plugin_count() * sizeof(void *);
2018 MYSQLND_MEMORY_POOL * pool = result->memory_pool;
2019 MYSQLND_RES_BUFFERED_C * ret;
2020
2021 DBG_ENTER("mysqlnd_result_buffered_c_init");
2022
2023 ret = pool->get_chunk(pool, alloc_size);
2024 memset(ret, 0, alloc_size);
2025
2026 if (FAIL == mysqlnd_error_info_init(&ret->error_info, 0)) {
2027 DBG_RETURN(NULL);
2028 }
2029
2030 ret->lengths = pool->get_chunk(pool, field_count * sizeof(size_t));
2031 memset(ret->lengths, 0, field_count * sizeof(size_t));
2032
2033 ret->result_set_memory_pool = pool;
2034 ret->field_count= field_count;
2035 ret->ps = ps;
2036 ret->m = *mysqlnd_result_buffered_get_methods();
2037 ret->type = MYSQLND_BUFFERED_TYPE_C;
2038
2039 if (ps) {
2040 ret->m.fetch_lengths = NULL; /* makes no sense */
2041 ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
2042 } else {
2043 ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol_c;
2044 }
2045 ret->m.fetch_row = MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_row);
2046 ret->m.fetch_lengths = MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_lengths);
2047 ret->m.data_seek = MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek);
2048 ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_c, initialize_result_set_rest);
2049
2050 DBG_RETURN(ret);
2051 }
2052 /* }}} */
2053
2054
2055 /*
2056 * Local variables:
2057 * tab-width: 4
2058 * c-basic-offset: 4
2059 * End:
2060 * vim600: noet sw=4 ts=4 fdm=marker
2061 * vim<600: noet sw=4 ts=4
2062 */
2063