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