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