1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2006-2014 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@mysql.com> |
16 | Ulf Wendel <uwendel@mysql.com> |
17 | Georg Richter <georg@mysql.com> |
18 +----------------------------------------------------------------------+
19 */
20
21 /* $Id$ */
22 #include "php.h"
23 #include "mysqlnd.h"
24 #include "mysqlnd_wireprotocol.h"
25 #include "mysqlnd_block_alloc.h"
26 #include "mysqlnd_priv.h"
27 #include "mysqlnd_result.h"
28 #include "mysqlnd_result_meta.h"
29 #include "mysqlnd_statistics.h"
30 #include "mysqlnd_debug.h"
31 #include "mysqlnd_ext_plugin.h"
32
33 #define MYSQLND_SILENT
34
35
36 /* {{{ mysqlnd_res::initialize_result_set_rest */
37 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,initialize_result_set_rest)38 MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest)(MYSQLND_RES * const result TSRMLS_DC)
39 {
40 unsigned int i;
41 zval **data_cursor = result->stored_data? result->stored_data->data:NULL;
42 zval **data_begin = result->stored_data? result->stored_data->data:NULL;
43 unsigned int field_count = result->meta? result->meta->field_count : 0;
44 uint64_t row_count = result->stored_data? result->stored_data->row_count:0;
45 enum_func_status ret = PASS;
46 DBG_ENTER("mysqlnd_res::initialize_result_set_rest");
47
48 if (!data_cursor || row_count == result->stored_data->initialized_rows) {
49 DBG_RETURN(ret);
50 }
51 while ((data_cursor - data_begin) < (int)(row_count * field_count)) {
52 if (NULL == data_cursor[0]) {
53 enum_func_status rc = result->m.row_decoder(
54 result->stored_data->row_buffers[(data_cursor - data_begin) / field_count],
55 data_cursor,
56 result->meta->field_count,
57 result->meta->fields,
58 result->conn->options->numeric_and_datetime_as_unicode,
59 result->conn->options->int_and_float_native,
60 result->conn->stats TSRMLS_CC);
61 if (rc != PASS) {
62 ret = FAIL;
63 break;
64 }
65 result->stored_data->initialized_rows++;
66 for (i = 0; i < result->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_P(data_cursor[i]) >= IS_STRING) {
73 unsigned long len = Z_STRLEN_P(data_cursor[i]);
74 if (result->meta->fields[i].max_length < len) {
75 result->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_rset_zval_ptr_dtor */
88 static void
mysqlnd_rset_zval_ptr_dtor(zval ** zv,enum_mysqlnd_res_type type,zend_bool * copy_ctor_called TSRMLS_DC)89 mysqlnd_rset_zval_ptr_dtor(zval **zv, enum_mysqlnd_res_type type, zend_bool * copy_ctor_called TSRMLS_DC)
90 {
91 DBG_ENTER("mysqlnd_rset_zval_ptr_dtor");
92 if (!zv || !*zv) {
93 *copy_ctor_called = FALSE;
94 DBG_ERR_FMT("zv was NULL");
95 DBG_VOID_RETURN;
96 }
97 /*
98 This zval is not from the cache block.
99 Thus the refcount is -1 than of a zval from the cache,
100 because the zvals from the cache are owned by it.
101 */
102 if (type == MYSQLND_RES_PS_BUF || type == MYSQLND_RES_PS_UNBUF) {
103 *copy_ctor_called = FALSE;
104 ; /* do nothing, zval_ptr_dtor will do the job*/
105 } else if (Z_REFCOUNT_PP(zv) > 1) {
106 /*
107 Not a prepared statement, then we have to
108 call copy_ctor and then zval_ptr_dtor()
109
110 In Unicode mode the destruction of the zvals should not call
111 zval_copy_ctor() because then we will leak.
112 I suppose we can use UG(unicode) in mysqlnd.c when freeing a result set
113 to check if we need to call copy_ctor().
114
115 If the type is IS_UNICODE, which can happen with PHP6, then we don't
116 need to copy_ctor, as the data doesn't point to our internal buffers.
117 If it's string (in PHP5 always) and in PHP6 if data is binary, then
118 it still points to internal buffers and has to be copied.
119 */
120 if (Z_TYPE_PP(zv) == IS_STRING) {
121 zval_copy_ctor(*zv);
122 }
123 *copy_ctor_called = TRUE;
124 } else {
125 /*
126 noone but us point to this, so we can safely ZVAL_NULL the zval,
127 so Zend does not try to free what the zval points to - which is
128 in result set buffers
129 */
130 *copy_ctor_called = FALSE;
131 if (Z_TYPE_PP(zv) == IS_STRING) {
132 ZVAL_NULL(*zv);
133 }
134 }
135 zval_ptr_dtor(zv);
136 DBG_VOID_RETURN;
137 }
138 /* }}} */
139
140
141 /* {{{ mysqlnd_res::unbuffered_free_last_data */
142 static void
MYSQLND_METHOD(mysqlnd_res,unbuffered_free_last_data)143 MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data)(MYSQLND_RES * result TSRMLS_DC)
144 {
145 MYSQLND_RES_UNBUFFERED *unbuf = result->unbuf;
146
147 DBG_ENTER("mysqlnd_res::unbuffered_free_last_data");
148
149 if (!unbuf) {
150 DBG_VOID_RETURN;
151 }
152
153 if (unbuf->last_row_data) {
154 unsigned int i, ctor_called_count = 0;
155 zend_bool copy_ctor_called;
156 MYSQLND_STATS *global_stats = result->conn? result->conn->stats:NULL;
157
158 for (i = 0; i < result->field_count; i++) {
159 mysqlnd_rset_zval_ptr_dtor(&(unbuf->last_row_data[i]), result->type, ©_ctor_called TSRMLS_CC);
160 if (copy_ctor_called) {
161 ++ctor_called_count;
162 }
163 }
164 DBG_INF_FMT("copy_ctor_called_count=%u", ctor_called_count);
165 /* By using value3 macros we hold a mutex only once, there is no value2 */
166 MYSQLND_INC_CONN_STATISTIC_W_VALUE2(global_stats,
167 STAT_COPY_ON_WRITE_PERFORMED,
168 ctor_called_count,
169 STAT_COPY_ON_WRITE_SAVED,
170 result->field_count - ctor_called_count);
171 /* Free last row's zvals */
172 mnd_efree(unbuf->last_row_data);
173 unbuf->last_row_data = NULL;
174 }
175 if (unbuf->last_row_buffer) {
176 DBG_INF("Freeing last row buffer");
177 /* Nothing points to this buffer now, free it */
178 unbuf->last_row_buffer->free_chunk(unbuf->last_row_buffer TSRMLS_CC);
179 unbuf->last_row_buffer = NULL;
180 }
181
182 DBG_VOID_RETURN;
183 }
184 /* }}} */
185
186
187 /* {{{ mysqlnd_res::free_buffered_data */
188 static void
MYSQLND_METHOD(mysqlnd_res,free_buffered_data)189 MYSQLND_METHOD(mysqlnd_res, free_buffered_data)(MYSQLND_RES * result TSRMLS_DC)
190 {
191 MYSQLND_RES_BUFFERED *set = result->stored_data;
192 unsigned int field_count = result->field_count;
193 int64_t row;
194
195 DBG_ENTER("mysqlnd_res::free_buffered_data");
196 DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count);
197
198 if (set->data) {
199 unsigned int copy_on_write_performed = 0;
200 unsigned int copy_on_write_saved = 0;
201 zval **data = set->data;
202 set->data = NULL; /* prevent double free if following loop is interrupted */
203
204 for (row = set->row_count - 1; row >= 0; row--) {
205 zval **current_row = data + row * field_count;
206 MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row];
207 int64_t col;
208
209 if (current_row != NULL) {
210 for (col = field_count - 1; col >= 0; --col) {
211 if (current_row[col]) {
212 zend_bool copy_ctor_called;
213 mysqlnd_rset_zval_ptr_dtor(&(current_row[col]), result->type, ©_ctor_called TSRMLS_CC);
214 if (copy_ctor_called) {
215 ++copy_on_write_performed;
216 } else {
217 ++copy_on_write_saved;
218 }
219 }
220 }
221 }
222 current_buffer->free_chunk(current_buffer TSRMLS_CC);
223 }
224
225 MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_COPY_ON_WRITE_PERFORMED, copy_on_write_performed,
226 STAT_COPY_ON_WRITE_SAVED, copy_on_write_saved);
227 mnd_efree(data);
228 }
229
230 if (set->row_buffers) {
231 mnd_efree(set->row_buffers);
232 set->row_buffers = NULL;
233 }
234 set->data_cursor = NULL;
235 set->row_count = 0;
236
237 mnd_efree(set);
238
239 DBG_VOID_RETURN;
240 }
241 /* }}} */
242
243
244 /* {{{ mysqlnd_res::free_result_buffers */
245 static void
MYSQLND_METHOD(mysqlnd_res,free_result_buffers)246 MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES * result TSRMLS_DC)
247 {
248 DBG_ENTER("mysqlnd_res::free_result_buffers");
249 DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown"));
250
251 if (result->unbuf) {
252 result->m.unbuffered_free_last_data(result TSRMLS_CC);
253 mnd_efree(result->unbuf);
254 result->unbuf = NULL;
255 } else if (result->stored_data) {
256 result->m.free_buffered_data(result TSRMLS_CC);
257 result->stored_data = NULL;
258 }
259
260 if (result->lengths) {
261 mnd_efree(result->lengths);
262 result->lengths = NULL;
263 }
264
265 if (result->row_packet) {
266 PACKET_FREE(result->row_packet);
267 result->row_packet = NULL;
268 }
269
270 if (result->result_set_memory_pool) {
271 mysqlnd_mempool_destroy(result->result_set_memory_pool TSRMLS_CC);
272 result->result_set_memory_pool = NULL;
273 }
274
275 DBG_VOID_RETURN;
276 }
277 /* }}} */
278
279
280 /* {{{ mysqlnd_internal_free_result_contents */
281 static
mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC)282 void mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC)
283 {
284 DBG_ENTER("mysqlnd_internal_free_result_contents");
285
286 result->m.free_result_buffers(result TSRMLS_CC);
287
288 if (result->meta) {
289 result->meta->m->free_metadata(result->meta TSRMLS_CC);
290 result->meta = NULL;
291 }
292
293 DBG_VOID_RETURN;
294 }
295 /* }}} */
296
297
298 /* {{{ mysqlnd_internal_free_result */
299 static
mysqlnd_internal_free_result(MYSQLND_RES * result TSRMLS_DC)300 void mysqlnd_internal_free_result(MYSQLND_RES * result TSRMLS_DC)
301 {
302 DBG_ENTER("mysqlnd_internal_free_result");
303 result->m.free_result_contents(result TSRMLS_CC);
304
305 if (result->conn) {
306 result->conn->m->free_reference(result->conn TSRMLS_CC);
307 result->conn = NULL;
308 }
309
310 mnd_pefree(result, result->persistent);
311
312 DBG_VOID_RETURN;
313 }
314 /* }}} */
315
316
317 /* {{{ mysqlnd_res::read_result_metadata */
318 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,read_result_metadata)319 MYSQLND_METHOD(mysqlnd_res, read_result_metadata)(MYSQLND_RES * result, MYSQLND_CONN_DATA * conn TSRMLS_DC)
320 {
321 DBG_ENTER("mysqlnd_res::read_result_metadata");
322
323 /*
324 Make it safe to call it repeatedly for PS -
325 better free and allocate a new because the number of field might change
326 (select *) with altered table. Also for statements which skip the PS
327 infrastructure!
328 */
329 if (result->meta) {
330 result->meta->m->free_metadata(result->meta TSRMLS_CC);
331 result->meta = NULL;
332 }
333
334 result->meta = result->m.result_meta_init(result->field_count, result->persistent TSRMLS_CC);
335 if (!result->meta) {
336 SET_OOM_ERROR(*conn->error_info);
337 DBG_RETURN(FAIL);
338 }
339
340 /* 1. Read all fields metadata */
341
342 /* It's safe to reread without freeing */
343 if (FAIL == result->meta->m->read_metadata(result->meta, conn TSRMLS_CC)) {
344 result->m.free_result_contents(result TSRMLS_CC);
345 DBG_RETURN(FAIL);
346 }
347 /* COM_FIELD_LIST is broken and has premature EOF, thus we need to hack here and in mysqlnd_res_meta.c */
348 result->field_count = result->meta->field_count;
349
350 /*
351 2. Follows an EOF packet, which the client of mysqlnd_read_result_metadata()
352 should consume.
353 3. If there is a result set, it follows. The last packet will have 'eof' set
354 If PS, then no result set follows.
355 */
356
357 DBG_RETURN(PASS);
358 }
359 /* }}} */
360
361
362 /* {{{ mysqlnd_query_read_result_set_header */
363 enum_func_status
mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn,MYSQLND_STMT * s TSRMLS_DC)364 mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s TSRMLS_DC)
365 {
366 MYSQLND_STMT_DATA * stmt = s ? s->data:NULL;
367 enum_func_status ret;
368 MYSQLND_PACKET_RSET_HEADER * rset_header = NULL;
369 MYSQLND_PACKET_EOF * fields_eof = NULL;
370
371 DBG_ENTER("mysqlnd_query_read_result_set_header");
372 DBG_INF_FMT("stmt=%lu", stmt? stmt->stmt_id:0);
373
374 ret = FAIL;
375 do {
376 rset_header = conn->protocol->m.get_rset_header_packet(conn->protocol, FALSE TSRMLS_CC);
377 if (!rset_header) {
378 SET_OOM_ERROR(*conn->error_info);
379 ret = FAIL;
380 break;
381 }
382
383 SET_ERROR_AFF_ROWS(conn);
384
385 if (FAIL == (ret = PACKET_READ(rset_header, conn))) {
386 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error reading result set's header");
387 break;
388 }
389
390 if (rset_header->error_info.error_no) {
391 /*
392 Cover a protocol design error: error packet does not
393 contain the server status. Therefore, the client has no way
394 to find out whether there are more result sets of
395 a multiple-result-set statement pending. Luckily, in 5.0 an
396 error always aborts execution of a statement, wherever it is
397 a multi-statement or a stored procedure, so it should be
398 safe to unconditionally turn off the flag here.
399 */
400 conn->upsert_status->server_status &= ~SERVER_MORE_RESULTS_EXISTS;
401 /*
402 This will copy the error code and the messages, as they
403 are buffers in the struct
404 */
405 COPY_CLIENT_ERROR(*conn->error_info, rset_header->error_info);
406 ret = FAIL;
407 DBG_ERR_FMT("error=%s", rset_header->error_info.error);
408 /* Return back from CONN_QUERY_SENT */
409 CONN_SET_STATE(conn, CONN_READY);
410 break;
411 }
412 conn->error_info->error_no = 0;
413
414 switch (rset_header->field_count) {
415 case MYSQLND_NULL_LENGTH: { /* LOAD DATA LOCAL INFILE */
416 zend_bool is_warning;
417 DBG_INF("LOAD DATA");
418 conn->last_query_type = QUERY_LOAD_LOCAL;
419 conn->field_count = 0; /* overwrite previous value, or the last value could be used and lead to bug#53503 */
420 CONN_SET_STATE(conn, CONN_SENDING_LOAD_DATA);
421 ret = mysqlnd_handle_local_infile(conn, rset_header->info_or_local_file, &is_warning TSRMLS_CC);
422 CONN_SET_STATE(conn, (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT);
423 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
424 break;
425 }
426 case 0: /* UPSERT */
427 DBG_INF("UPSERT");
428 conn->last_query_type = QUERY_UPSERT;
429 conn->field_count = rset_header->field_count;
430 memset(conn->upsert_status, 0, sizeof(*conn->upsert_status));
431 conn->upsert_status->warning_count = rset_header->warning_count;
432 conn->upsert_status->server_status = rset_header->server_status;
433 conn->upsert_status->affected_rows = rset_header->affected_rows;
434 conn->upsert_status->last_insert_id = rset_header->last_insert_id;
435 SET_NEW_MESSAGE(conn->last_message, conn->last_message_len,
436 rset_header->info_or_local_file, rset_header->info_or_local_file_len,
437 conn->persistent);
438 /* Result set can follow UPSERT statement, check server_status */
439 if (conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
440 CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
441 } else {
442 CONN_SET_STATE(conn, CONN_READY);
443 }
444 ret = PASS;
445 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
446 break;
447 default: do { /* Result set */
448 MYSQLND_RES * result;
449 enum_mysqlnd_collected_stats statistic = STAT_LAST;
450
451 DBG_INF("Result set pending");
452 SET_EMPTY_MESSAGE(conn->last_message, conn->last_message_len, conn->persistent);
453
454 MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_RSET_QUERY);
455 memset(conn->upsert_status, 0, sizeof(*conn->upsert_status));
456 /* restore after zeroing */
457 SET_ERROR_AFF_ROWS(conn);
458
459 conn->last_query_type = QUERY_SELECT;
460 CONN_SET_STATE(conn, CONN_FETCHING_DATA);
461 /* PS has already allocated it */
462 conn->field_count = rset_header->field_count;
463 if (!stmt) {
464 result = conn->current_result = conn->m->result_init(rset_header->field_count, conn->persistent TSRMLS_CC);
465 } else {
466 if (!stmt->result) {
467 DBG_INF("This is 'SHOW'/'EXPLAIN'-like query.");
468 /*
469 This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
470 prepared statements can't send result set metadata for these queries
471 on prepare stage. Read it now.
472 */
473 result = stmt->result = conn->m->result_init(rset_header->field_count, stmt->persistent TSRMLS_CC);
474 } else {
475 /*
476 Update result set metadata if it for some reason changed between
477 prepare and execute, i.e.:
478 - in case of 'SELECT ?' we don't know column type unless data was
479 supplied to mysql_stmt_execute, so updated column type is sent
480 now.
481 - if data dictionary changed between prepare and execute, for
482 example a table used in the query was altered.
483 Note, that now (4.1.3) we always send metadata in reply to
484 COM_STMT_EXECUTE (even if it is not necessary), so either this or
485 previous branch always works.
486 */
487 }
488 result = stmt->result;
489 }
490 if (!result) {
491 SET_OOM_ERROR(*conn->error_info);
492 ret = FAIL;
493 break;
494 }
495
496 if (FAIL == (ret = result->m.read_result_metadata(result, conn TSRMLS_CC))) {
497 /* For PS, we leave them in Prepared state */
498 if (!stmt && conn->current_result) {
499 mnd_efree(conn->current_result);
500 conn->current_result = NULL;
501 }
502 DBG_ERR("Error occurred while reading metadata");
503 break;
504 }
505
506 /* Check for SERVER_STATUS_MORE_RESULTS if needed */
507 fields_eof = conn->protocol->m.get_eof_packet(conn->protocol, FALSE TSRMLS_CC);
508 if (!fields_eof) {
509 SET_OOM_ERROR(*conn->error_info);
510 ret = FAIL;
511 break;
512 }
513 if (FAIL == (ret = PACKET_READ(fields_eof, conn))) {
514 DBG_ERR("Error occurred while reading the EOF packet");
515 result->m.free_result_contents(result TSRMLS_CC);
516 mnd_efree(result);
517 if (!stmt) {
518 conn->current_result = NULL;
519 } else {
520 stmt->result = NULL;
521 memset(stmt, 0, sizeof(*stmt));
522 stmt->state = MYSQLND_STMT_INITTED;
523 }
524 } else {
525 unsigned int to_log = MYSQLND_G(log_mask);
526 to_log &= fields_eof->server_status;
527 DBG_INF_FMT("warnings=%u server_status=%u", fields_eof->warning_count, fields_eof->server_status);
528 conn->upsert_status->warning_count = fields_eof->warning_count;
529 /*
530 If SERVER_MORE_RESULTS_EXISTS is set then this is either MULTI_QUERY or a CALL()
531 The first packet after sending the query/com_execute has the bit set only
532 in this cases. Not sure why it's a needed but it marks that the whole stream
533 will include many result sets. What actually matters are the bits set at the end
534 of every result set (the EOF packet).
535 */
536 conn->upsert_status->server_status = fields_eof->server_status;
537 if (fields_eof->server_status & SERVER_QUERY_NO_GOOD_INDEX_USED) {
538 statistic = STAT_BAD_INDEX_USED;
539 } else if (fields_eof->server_status & SERVER_QUERY_NO_INDEX_USED) {
540 statistic = STAT_NO_INDEX_USED;
541 } else if (fields_eof->server_status & SERVER_QUERY_WAS_SLOW) {
542 statistic = STAT_QUERY_WAS_SLOW;
543 }
544 if (to_log) {
545 #if A0
546 char *backtrace = mysqlnd_get_backtrace(TSRMLS_C);
547 php_log_err(backtrace TSRMLS_CC);
548 efree(backtrace);
549 #endif
550 }
551 MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
552 }
553 } while (0);
554 PACKET_FREE(fields_eof);
555 break; /* switch break */
556 }
557 } while (0);
558 PACKET_FREE(rset_header);
559
560 DBG_INF(ret == PASS? "PASS":"FAIL");
561 DBG_RETURN(ret);
562 }
563 /* }}} */
564
565
566 /* {{{ mysqlnd_fetch_lengths_buffered */
567 /*
568 Do lazy initialization for buffered results. As PHP strings have
569 length inside, this function makes not much sense in the context
570 of PHP, to be called as separate function. But let's have it for
571 completeness.
572 */
573 static unsigned long *
mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result TSRMLS_DC)574 mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result TSRMLS_DC)
575 {
576 unsigned int i;
577 zval **previous_row;
578 MYSQLND_RES_BUFFERED *set = result->stored_data;
579
580 /*
581 If:
582 - unbuffered result
583 - first row has not been read
584 - last_row has been read
585 */
586 if (set->data_cursor == NULL ||
587 set->data_cursor == set->data ||
588 ((set->data_cursor - set->data) > (set->row_count * result->meta->field_count) ))
589 {
590 return NULL;/* No rows or no more rows */
591 }
592
593 previous_row = set->data_cursor - result->meta->field_count;
594 for (i = 0; i < result->meta->field_count; i++) {
595 result->lengths[i] = (Z_TYPE_P(previous_row[i]) == IS_NULL)? 0:Z_STRLEN_P(previous_row[i]);
596 }
597
598 return result->lengths;
599 }
600 /* }}} */
601
602
603 /* {{{ mysqlnd_fetch_lengths_unbuffered */
604 static unsigned long *
mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result TSRMLS_DC)605 mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result TSRMLS_DC)
606 {
607 /* simulate output of libmysql */
608 return (!result->unbuf || result->unbuf->last_row_data || result->unbuf->eof_reached)? result->lengths:NULL;
609 }
610 /* }}} */
611
612
613 /* {{{ mysqlnd_res::fetch_lengths */
_mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC)614 PHPAPI unsigned long * _mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC)
615 {
616 return result->m.fetch_lengths? result->m.fetch_lengths(result TSRMLS_CC) : NULL;
617 }
618 /* }}} */
619
620
621 /* {{{ mysqlnd_fetch_row_unbuffered_c */
622 static MYSQLND_ROW_C
mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC)623 mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC)
624 {
625 enum_func_status ret;
626 MYSQLND_ROW_C retrow = NULL;
627 unsigned int i,
628 field_count = result->field_count;
629 MYSQLND_PACKET_ROW *row_packet = result->row_packet;
630 unsigned long *lengths = result->lengths;
631
632 DBG_ENTER("mysqlnd_fetch_row_unbuffered_c");
633
634 if (result->unbuf->eof_reached) {
635 /* No more rows obviously */
636 DBG_RETURN(retrow);
637 }
638 if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
639 SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
640 UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
641 DBG_RETURN(retrow);
642 }
643 if (!row_packet) {
644 /* Not fully initialized object that is being cleaned up */
645 DBG_RETURN(retrow);
646 }
647 /* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
648 row_packet->skip_extraction = FALSE;
649
650 /*
651 If we skip rows (row == NULL) we have to
652 result->m.unbuffered_free_last_data() before it. The function returns always true.
653 */
654 if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
655 result->unbuf->row_count++;
656
657 result->m.unbuffered_free_last_data(result TSRMLS_CC);
658
659 result->unbuf->last_row_data = row_packet->fields;
660 result->unbuf->last_row_buffer = row_packet->row_buffer;
661 row_packet->fields = NULL;
662 row_packet->row_buffer = NULL;
663
664 MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
665
666 if (!row_packet->skip_extraction) {
667 MYSQLND_FIELD *field = result->meta->fields;
668 struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
669
670 enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer,
671 result->unbuf->last_row_data,
672 row_packet->field_count,
673 row_packet->fields_metadata,
674 result->conn->options->numeric_and_datetime_as_unicode,
675 result->conn->options->int_and_float_native,
676 result->conn->stats TSRMLS_CC);
677 if (PASS != rc) {
678 DBG_RETURN(retrow);
679 }
680
681 retrow = mnd_malloc(result->field_count * sizeof(char *));
682 if (retrow) {
683 for (i = 0; i < field_count; i++, field++, hash_key++) {
684 zval *data = result->unbuf->last_row_data[i];
685 unsigned int len;
686
687 if (Z_TYPE_P(data) != IS_NULL) {
688 convert_to_string(data);
689 retrow[i] = Z_STRVAL_P(data);
690 len = Z_STRLEN_P(data);
691 } else {
692 retrow[i] = NULL;
693 len = 0;
694 }
695
696 if (lengths) {
697 lengths[i] = len;
698 }
699
700 if (field->max_length < len) {
701 field->max_length = len;
702 }
703 }
704 } else {
705 SET_OOM_ERROR(*result->conn->error_info);
706 }
707 }
708 } else if (ret == FAIL) {
709 if (row_packet->error_info.error_no) {
710 COPY_CLIENT_ERROR(*result->conn->error_info, row_packet->error_info);
711 DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
712 }
713 CONN_SET_STATE(result->conn, CONN_READY);
714 result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
715 } else if (row_packet->eof) {
716 /* Mark the connection as usable again */
717 DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
718 result->unbuf->eof_reached = TRUE;
719 memset(result->conn->upsert_status, 0, sizeof(*result->conn->upsert_status));
720 result->conn->upsert_status->warning_count = row_packet->warning_count;
721 result->conn->upsert_status->server_status = row_packet->server_status;
722 /*
723 result->row_packet will be cleaned when
724 destroying the result object
725 */
726 if (result->conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
727 CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
728 } else {
729 CONN_SET_STATE(result->conn, CONN_READY);
730 }
731 result->m.unbuffered_free_last_data(result TSRMLS_CC);
732 }
733
734 DBG_RETURN(retrow);
735 }
736 /* }}} */
737
738
739 /* {{{ mysqlnd_fetch_row_unbuffered */
740 static enum_func_status
mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)741 mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
742 {
743 enum_func_status ret;
744 zval *row = (zval *) param;
745 MYSQLND_PACKET_ROW *row_packet = result->row_packet;
746
747 DBG_ENTER("mysqlnd_fetch_row_unbuffered");
748
749 *fetched_anything = FALSE;
750 if (result->unbuf->eof_reached) {
751 /* No more rows obviously */
752 DBG_RETURN(PASS);
753 }
754 if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
755 SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
756 DBG_RETURN(FAIL);
757 }
758 if (!row_packet) {
759 /* Not fully initialized object that is being cleaned up */
760 DBG_RETURN(FAIL);
761 }
762 /* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
763 row_packet->skip_extraction = row? FALSE:TRUE;
764
765 /*
766 If we skip rows (row == NULL) we have to
767 result->m.unbuffered_free_last_data() before it. The function returns always true.
768 */
769 if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
770 result->m.unbuffered_free_last_data(result TSRMLS_CC);
771
772 result->unbuf->last_row_data = row_packet->fields;
773 result->unbuf->last_row_buffer = row_packet->row_buffer;
774 row_packet->fields = NULL;
775 row_packet->row_buffer = NULL;
776
777 MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
778
779 if (!row_packet->skip_extraction) {
780 HashTable *row_ht = Z_ARRVAL_P(row);
781 MYSQLND_FIELD *field = result->meta->fields;
782 struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
783 unsigned int i, field_count = result->field_count;
784 unsigned long *lengths = result->lengths;
785
786 enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer,
787 result->unbuf->last_row_data,
788 field_count,
789 row_packet->fields_metadata,
790 result->conn->options->numeric_and_datetime_as_unicode,
791 result->conn->options->int_and_float_native,
792 result->conn->stats TSRMLS_CC);
793 if (PASS != rc) {
794 DBG_RETURN(FAIL);
795 }
796 for (i = 0; i < field_count; i++, field++, hash_key++) {
797 zval *data = result->unbuf->last_row_data[i];
798 unsigned int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data);
799
800 if (lengths) {
801 lengths[i] = len;
802 }
803
804 if (flags & MYSQLND_FETCH_NUM) {
805 Z_ADDREF_P(data);
806 zend_hash_next_index_insert(row_ht, &data, sizeof(zval *), NULL);
807 }
808 if (flags & MYSQLND_FETCH_ASSOC) {
809 /* zend_hash_quick_update needs length + trailing zero */
810 /* QQ: Error handling ? */
811 /*
812 zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
813 the index is a numeric and convert it to it. This however means constant
814 hashing of the column name, which is not needed as it can be precomputed.
815 */
816 Z_ADDREF_P(data);
817 if (hash_key->is_numeric == FALSE) {
818 #if MYSQLND_UNICODE
819 zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE,
820 hash_key->ustr,
821 hash_key->ulen + 1,
822 hash_key->key,
823 (void *) &data, sizeof(zval *), NULL);
824 #else
825 zend_hash_quick_update(Z_ARRVAL_P(row),
826 field->name,
827 field->name_length + 1,
828 hash_key->key,
829 (void *) &data, sizeof(zval *), NULL);
830 #endif
831 } else {
832 zend_hash_index_update(Z_ARRVAL_P(row),
833 hash_key->key,
834 (void *) &data, sizeof(zval *), NULL);
835 }
836 }
837 if (field->max_length < len) {
838 field->max_length = len;
839 }
840 }
841 }
842 *fetched_anything = TRUE;
843 result->unbuf->row_count++;
844 } else if (ret == FAIL) {
845 if (row_packet->error_info.error_no) {
846 COPY_CLIENT_ERROR(*result->conn->error_info, row_packet->error_info);
847 DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
848 }
849 CONN_SET_STATE(result->conn, CONN_READY);
850 result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
851 } else if (row_packet->eof) {
852 /* Mark the connection as usable again */
853 DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
854 result->unbuf->eof_reached = TRUE;
855 memset(result->conn->upsert_status, 0, sizeof(*result->conn->upsert_status));
856 result->conn->upsert_status->warning_count = row_packet->warning_count;
857 result->conn->upsert_status->server_status = row_packet->server_status;
858 /*
859 result->row_packet will be cleaned when
860 destroying the result object
861 */
862 if (result->conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
863 CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
864 } else {
865 CONN_SET_STATE(result->conn, CONN_READY);
866 }
867 result->m.unbuffered_free_last_data(result TSRMLS_CC);
868 }
869
870 DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
871 DBG_RETURN(PASS);
872 }
873 /* }}} */
874
875
876 /* {{{ mysqlnd_res::use_result */
877 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,use_result)878 MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, zend_bool ps TSRMLS_DC)
879 {
880 DBG_ENTER("mysqlnd_res::use_result");
881
882 SET_EMPTY_ERROR(*result->conn->error_info);
883
884 if (ps == FALSE) {
885 result->type = MYSQLND_RES_NORMAL;
886 result->m.fetch_row = result->m.fetch_row_normal_unbuffered;
887 result->m.fetch_lengths = mysqlnd_fetch_lengths_unbuffered;
888 result->m.row_decoder = php_mysqlnd_rowp_read_text_protocol;
889 result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long));
890 if (!result->lengths) {
891 goto oom;
892 }
893 } else {
894 result->type = MYSQLND_RES_PS_UNBUF;
895 result->m.fetch_row = NULL;
896 /* result->m.fetch_row() will be set in mysqlnd_ps.c */
897 result->m.fetch_lengths = NULL; /* makes no sense */
898 result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol;
899 result->lengths = NULL;
900 }
901
902 result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC);
903 result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED));
904 if (!result->result_set_memory_pool || !result->unbuf) {
905 goto oom;
906 }
907
908 /*
909 Will be freed in the mysqlnd_internal_free_result_contents() called
910 by the resource destructor. mysqlnd_fetch_row_unbuffered() expects
911 this to be not NULL.
912 */
913 /* FALSE = non-persistent */
914 result->row_packet = result->conn->protocol->m.get_row_packet(result->conn->protocol, FALSE TSRMLS_CC);
915 if (!result->row_packet) {
916 goto oom;
917 }
918 result->row_packet->result_set_memory_pool = result->result_set_memory_pool;
919 result->row_packet->field_count = result->field_count;
920 result->row_packet->binary_protocol = ps;
921 result->row_packet->fields_metadata = result->meta->fields;
922 result->row_packet->bit_fields_count = result->meta->bit_fields_count;
923 result->row_packet->bit_fields_total_len = result->meta->bit_fields_total_len;
924
925 DBG_RETURN(result);
926 oom:
927 SET_OOM_ERROR(*result->conn->error_info);
928 DBG_RETURN(NULL);
929 }
930 /* }}} */
931
932
933 /* {{{ mysqlnd_fetch_row_buffered_c */
934 static MYSQLND_ROW_C
mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC)935 mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC)
936 {
937 MYSQLND_ROW_C ret = NULL;
938 MYSQLND_RES_BUFFERED *set = result->stored_data;
939
940 DBG_ENTER("mysqlnd_fetch_row_buffered_c");
941
942 /* If we haven't read everything */
943 if (set->data_cursor &&
944 (set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
945 {
946 zval **current_row = set->data_cursor;
947 MYSQLND_FIELD *field = result->meta->fields;
948 struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
949 unsigned int i;
950
951 if (NULL == current_row[0]) {
952 uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count;
953 enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num],
954 current_row,
955 result->meta->field_count,
956 result->meta->fields,
957 result->conn->options->numeric_and_datetime_as_unicode,
958 result->conn->options->int_and_float_native,
959 result->conn->stats TSRMLS_CC);
960 if (rc != PASS) {
961 DBG_RETURN(ret);
962 }
963 set->initialized_rows++;
964 for (i = 0; i < result->field_count; i++) {
965 /*
966 NULL fields are 0 length, 0 is not more than 0
967 String of zero size, definitely can't be the next max_length.
968 Thus for NULL and zero-length we are quite efficient.
969 */
970 if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
971 unsigned long len = Z_STRLEN_P(current_row[i]);
972 if (field->max_length < len) {
973 field->max_length = len;
974 }
975 }
976 }
977 }
978
979 set->data_cursor += result->meta->field_count;
980 MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
981
982 ret = mnd_malloc(result->field_count * sizeof(char *));
983 if (ret) {
984 for (i = 0; i < result->field_count; i++, field++, hash_key++) {
985 zval *data = current_row[i];
986
987 if (Z_TYPE_P(data) != IS_NULL) {
988 convert_to_string(data);
989 ret[i] = Z_STRVAL_P(data);
990 } else {
991 ret[i] = NULL;
992 }
993 }
994 }
995 /* there is no conn handle in this function thus we can't set OOM in error_info */
996 } else {
997 set->data_cursor = NULL;
998 DBG_INF("EOF reached");
999 }
1000 DBG_RETURN(ret);
1001 }
1002 /* }}} */
1003
1004
1005 /* {{{ mysqlnd_fetch_row_buffered */
1006 static enum_func_status
mysqlnd_fetch_row_buffered(MYSQLND_RES * result,void * param,unsigned int flags,zend_bool * fetched_anything TSRMLS_DC)1007 mysqlnd_fetch_row_buffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
1008 {
1009 unsigned int i;
1010 zval *row = (zval *) param;
1011 MYSQLND_RES_BUFFERED *set = result->stored_data;
1012 enum_func_status ret = FAIL;
1013
1014 DBG_ENTER("mysqlnd_fetch_row_buffered");
1015
1016 /* If we haven't read everything */
1017 if (set->data_cursor &&
1018 (set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
1019 {
1020 zval **current_row = set->data_cursor;
1021 MYSQLND_FIELD *field = result->meta->fields;
1022 struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
1023
1024 if (NULL == current_row[0]) {
1025 uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count;
1026 enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num],
1027 current_row,
1028 result->meta->field_count,
1029 result->meta->fields,
1030 result->conn->options->numeric_and_datetime_as_unicode,
1031 result->conn->options->int_and_float_native,
1032 result->conn->stats TSRMLS_CC);
1033 if (rc != PASS) {
1034 DBG_RETURN(FAIL);
1035 }
1036 set->initialized_rows++;
1037 for (i = 0; i < result->field_count; i++) {
1038 /*
1039 NULL fields are 0 length, 0 is not more than 0
1040 String of zero size, definitely can't be the next max_length.
1041 Thus for NULL and zero-length we are quite efficient.
1042 */
1043 if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
1044 unsigned long len = Z_STRLEN_P(current_row[i]);
1045 if (field->max_length < len) {
1046 field->max_length = len;
1047 }
1048 }
1049 }
1050 }
1051
1052 for (i = 0; i < result->field_count; i++, field++, hash_key++) {
1053 zval *data = current_row[i];
1054
1055 if (flags & MYSQLND_FETCH_NUM) {
1056 Z_ADDREF_P(data);
1057 zend_hash_next_index_insert(Z_ARRVAL_P(row), &data, sizeof(zval *), NULL);
1058 }
1059 if (flags & MYSQLND_FETCH_ASSOC) {
1060 /* zend_hash_quick_update needs length + trailing zero */
1061 /* QQ: Error handling ? */
1062 /*
1063 zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
1064 the index is a numeric and convert it to it. This however means constant
1065 hashing of the column name, which is not needed as it can be precomputed.
1066 */
1067 Z_ADDREF_P(data);
1068 if (hash_key->is_numeric == FALSE) {
1069 #if MYSQLND_UNICODE
1070 zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE,
1071 hash_key->ustr,
1072 hash_key->ulen + 1,
1073 hash_key->key,
1074 (void *) &data, sizeof(zval *), NULL);
1075 #else
1076 zend_hash_quick_update(Z_ARRVAL_P(row),
1077 field->name,
1078 field->name_length + 1,
1079 hash_key->key,
1080 (void *) &data, sizeof(zval *), NULL);
1081 #endif
1082 } else {
1083 zend_hash_index_update(Z_ARRVAL_P(row),
1084 hash_key->key,
1085 (void *) &data, sizeof(zval *), NULL);
1086 }
1087 }
1088 }
1089 set->data_cursor += result->meta->field_count;
1090 *fetched_anything = TRUE;
1091 MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
1092 ret = PASS;
1093 } else {
1094 set->data_cursor = NULL;
1095 *fetched_anything = FALSE;
1096 ret = PASS;
1097 DBG_INF("EOF reached");
1098 }
1099 DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
1100 DBG_RETURN(ret);
1101 }
1102 /* }}} */
1103
1104
1105 #define STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY 2
1106
1107 /* {{{ mysqlnd_res::store_result_fetch_data */
1108 enum_func_status
MYSQLND_METHOD(mysqlnd_res,store_result_fetch_data)1109 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result,
1110 MYSQLND_RES_METADATA *meta,
1111 zend_bool binary_protocol TSRMLS_DC)
1112 {
1113 enum_func_status ret;
1114 MYSQLND_PACKET_ROW *row_packet = NULL;
1115 unsigned int next_extend = STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY, free_rows = 1;
1116 MYSQLND_RES_BUFFERED *set;
1117
1118 DBG_ENTER("mysqlnd_res::store_result_fetch_data");
1119
1120 result->stored_data = set = mnd_ecalloc(1, sizeof(MYSQLND_RES_BUFFERED));
1121 if (!set) {
1122 SET_OOM_ERROR(*conn->error_info);
1123 ret = FAIL;
1124 goto end;
1125 }
1126 if (free_rows) {
1127 set->row_buffers = mnd_emalloc((size_t)(free_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)));
1128 if (!set->row_buffers) {
1129 SET_OOM_ERROR(*conn->error_info);
1130 ret = FAIL;
1131 goto end;
1132 }
1133 }
1134 set->references = 1;
1135
1136 /* non-persistent */
1137 row_packet = conn->protocol->m.get_row_packet(conn->protocol, FALSE TSRMLS_CC);
1138 if (!row_packet) {
1139 SET_OOM_ERROR(*conn->error_info);
1140 ret = FAIL;
1141 goto end;
1142 }
1143 row_packet->result_set_memory_pool = result->result_set_memory_pool;
1144 row_packet->field_count = meta->field_count;
1145 row_packet->binary_protocol = binary_protocol;
1146 row_packet->fields_metadata = meta->fields;
1147 row_packet->bit_fields_count = meta->bit_fields_count;
1148 row_packet->bit_fields_total_len = meta->bit_fields_total_len;
1149
1150 row_packet->skip_extraction = TRUE; /* let php_mysqlnd_rowp_read() not allocate row_packet->fields, we will do it */
1151
1152 while (FAIL != (ret = PACKET_READ(row_packet, conn)) && !row_packet->eof) {
1153 if (!free_rows) {
1154 uint64_t total_allocated_rows = free_rows = next_extend = next_extend * 11 / 10; /* extend with 10% */
1155 MYSQLND_MEMORY_POOL_CHUNK ** new_row_buffers;
1156 total_allocated_rows += set->row_count;
1157
1158 /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1159 if (total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) {
1160 SET_OOM_ERROR(*conn->error_info);
1161 ret = FAIL;
1162 goto end;
1163 }
1164 new_row_buffers = mnd_erealloc(set->row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)));
1165 if (!new_row_buffers) {
1166 SET_OOM_ERROR(*conn->error_info);
1167 ret = FAIL;
1168 goto end;
1169 }
1170 set->row_buffers = new_row_buffers;
1171 }
1172 free_rows--;
1173 set->row_buffers[set->row_count] = row_packet->row_buffer;
1174
1175 set->row_count++;
1176
1177 /* So row_packet's destructor function won't efree() it */
1178 row_packet->fields = NULL;
1179 row_packet->row_buffer = NULL;
1180
1181 /*
1182 No need to FREE_ALLOCA as we can reuse the
1183 'lengths' and 'fields' arrays. For lengths its absolutely safe.
1184 'fields' is reused because the ownership of the strings has been
1185 transferred above.
1186 */
1187 }
1188 /* Overflow ? */
1189 if (set->row_count) {
1190 /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1191 if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) {
1192 SET_OOM_ERROR(*conn->error_info);
1193 ret = FAIL;
1194 goto end;
1195 }
1196 /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */
1197 set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval *)));
1198 if (!set->data) {
1199 SET_OOM_ERROR(*conn->error_info);
1200 ret = FAIL;
1201 goto end;
1202 }
1203 memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval *)));
1204 }
1205
1206 MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats,
1207 binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
1208 STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
1209 set->row_count);
1210
1211 /* Finally clean */
1212 if (row_packet->eof) {
1213 memset(conn->upsert_status, 0, sizeof(*conn->upsert_status));
1214 conn->upsert_status->warning_count = row_packet->warning_count;
1215 conn->upsert_status->server_status = row_packet->server_status;
1216 }
1217 /* save some memory */
1218 if (free_rows) {
1219 /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
1220 if (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) {
1221 SET_OOM_ERROR(*conn->error_info);
1222 ret = FAIL;
1223 goto end;
1224 }
1225 set->row_buffers = mnd_erealloc(set->row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)));
1226 }
1227
1228 if (conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) {
1229 CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
1230 } else {
1231 CONN_SET_STATE(conn, CONN_READY);
1232 }
1233
1234 if (ret == FAIL) {
1235 COPY_CLIENT_ERROR(set->error_info, row_packet->error_info);
1236 } else {
1237 /* Position at the first row */
1238 set->data_cursor = set->data;
1239
1240 /* libmysql's documentation says it should be so for SELECT statements */
1241 conn->upsert_status->affected_rows = set->row_count;
1242 }
1243 DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u",
1244 ret == PASS? "PASS":"FAIL", (uint) set->row_count, conn->upsert_status->warning_count, conn->upsert_status->server_status);
1245 end:
1246 PACKET_FREE(row_packet);
1247
1248 DBG_RETURN(ret);
1249 }
1250 /* }}} */
1251
1252
1253 /* {{{ mysqlnd_res::store_result */
1254 static MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_res,store_result)1255 MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
1256 MYSQLND_CONN_DATA * const conn,
1257 zend_bool ps_protocol TSRMLS_DC)
1258 {
1259 enum_func_status ret;
1260
1261 DBG_ENTER("mysqlnd_res::store_result");
1262
1263 /* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
1264 result->conn = conn->m->get_reference(conn TSRMLS_CC);
1265 result->type = MYSQLND_RES_NORMAL;
1266 result->m.fetch_row = result->m.fetch_row_normal_buffered;
1267 result->m.fetch_lengths = mysqlnd_fetch_lengths_buffered;
1268 result->m.row_decoder = ps_protocol? php_mysqlnd_rowp_read_binary_protocol:
1269 php_mysqlnd_rowp_read_text_protocol;
1270
1271 result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC);
1272 result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long));
1273
1274 if (!result->result_set_memory_pool || !result->lengths) {
1275 SET_OOM_ERROR(*conn->error_info);
1276 DBG_RETURN(NULL);
1277 }
1278
1279 CONN_SET_STATE(conn, CONN_FETCHING_DATA);
1280
1281 ret = result->m.store_result_fetch_data(conn, result, result->meta, ps_protocol TSRMLS_CC);
1282 if (FAIL == ret) {
1283 if (result->stored_data) {
1284 COPY_CLIENT_ERROR(*conn->error_info, result->stored_data->error_info);
1285 } else {
1286 SET_OOM_ERROR(*conn->error_info);
1287 }
1288 DBG_RETURN(NULL);
1289 }
1290 /* libmysql's documentation says it should be so for SELECT statements */
1291 conn->upsert_status->affected_rows = result->stored_data->row_count;
1292
1293 DBG_RETURN(result);
1294 }
1295 /* }}} */
1296
1297
1298 /* {{{ mysqlnd_res::skip_result */
1299 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,skip_result)1300 MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result TSRMLS_DC)
1301 {
1302 zend_bool fetched_anything;
1303
1304 DBG_ENTER("mysqlnd_res::skip_result");
1305 /*
1306 Unbuffered sets
1307 A PS could be prepared - there is metadata and thus a stmt->result but the
1308 fetch_row function isn't actually set (NULL), thus we have to skip these.
1309 */
1310 if (!result->stored_data && result->unbuf &&
1311 !result->unbuf->eof_reached && result->m.fetch_row)
1312 {
1313 DBG_INF("skipping result");
1314 /* We have to fetch all data to clean the line */
1315 MYSQLND_INC_CONN_STATISTIC(result->conn->stats,
1316 result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS:
1317 STAT_FLUSHED_PS_SETS);
1318
1319 while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything TSRMLS_CC)) && fetched_anything == TRUE) {
1320 /* do nothing */;
1321 }
1322 }
1323 DBG_RETURN(PASS);
1324 }
1325 /* }}} */
1326
1327
1328 /* {{{ mysqlnd_res::free_result */
1329 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,free_result)1330 MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC)
1331 {
1332 DBG_ENTER("mysqlnd_res::free_result");
1333
1334 result->m.skip_result(result TSRMLS_CC);
1335 MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL,
1336 implicit == TRUE? STAT_FREE_RESULT_IMPLICIT:
1337 STAT_FREE_RESULT_EXPLICIT);
1338
1339 result->m.free_result_internal(result TSRMLS_CC);
1340 DBG_RETURN(PASS);
1341 }
1342 /* }}} */
1343
1344
1345 /* {{{ mysqlnd_res::data_seek */
1346 static enum_func_status
MYSQLND_METHOD(mysqlnd_res,data_seek)1347 MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * result, uint64_t row TSRMLS_DC)
1348 {
1349 DBG_ENTER("mysqlnd_res::data_seek");
1350 DBG_INF_FMT("row=%lu", row);
1351
1352 if (!result->stored_data) {
1353 return FAIL;
1354 }
1355
1356 /* libmysql just moves to the end, it does traversing of a linked list */
1357 if (row >= result->stored_data->row_count) {
1358 result->stored_data->data_cursor = NULL;
1359 } else {
1360 result->stored_data->data_cursor = result->stored_data->data + row * result->meta->field_count;
1361 }
1362
1363 DBG_RETURN(PASS);
1364 }
1365 /* }}} */
1366
1367
1368 /* {{{ mysqlnd_res::num_rows */
1369 static uint64_t
MYSQLND_METHOD(mysqlnd_res,num_rows)1370 MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result TSRMLS_DC)
1371 {
1372 /* Be compatible with libmysql. We count row_count, but will return 0 */
1373 return result->stored_data? result->stored_data->row_count:(result->unbuf && result->unbuf->eof_reached? result->unbuf->row_count:0);
1374 }
1375 /* }}} */
1376
1377
1378 /* {{{ mysqlnd_res::num_fields */
1379 static unsigned int
MYSQLND_METHOD(mysqlnd_res,num_fields)1380 MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result TSRMLS_DC)
1381 {
1382 return result->field_count;
1383 }
1384 /* }}} */
1385
1386
1387 /* {{{ mysqlnd_res::fetch_field */
1388 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field)1389 MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result TSRMLS_DC)
1390 {
1391 DBG_ENTER("mysqlnd_res::fetch_field");
1392 do {
1393 if (result->meta) {
1394 /*
1395 We optimize the result set, so we don't convert all the data from raw buffer format to
1396 zval arrays during store. In the case someone doesn't read all the lines this will
1397 save time. However, when a metadata call is done, we need to calculate max_length.
1398 We don't have control whether max_length will be used, unfortunately. Otherwise we
1399 could have been able to skip that step.
1400 Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1401 then we can have max_length as dynamic property, which will be calculated during runtime and
1402 not during mysqli_fetch_field() time.
1403 */
1404 if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1405 DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1406 /* we have to initialize the rest to get the updated max length */
1407 if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
1408 break;
1409 }
1410 }
1411 DBG_RETURN(result->meta->m->fetch_field(result->meta TSRMLS_CC));
1412 }
1413 } while (0);
1414 DBG_RETURN(NULL);
1415 }
1416 /* }}} */
1417
1418
1419 /* {{{ mysqlnd_res::fetch_field_direct */
1420 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_field_direct)1421 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC)
1422 {
1423 DBG_ENTER("mysqlnd_res::fetch_field_direct");
1424 do {
1425 if (result->meta) {
1426 /*
1427 We optimize the result set, so we don't convert all the data from raw buffer format to
1428 zval arrays during store. In the case someone doesn't read all the lines this will
1429 save time. However, when a metadata call is done, we need to calculate max_length.
1430 We don't have control whether max_length will be used, unfortunately. Otherwise we
1431 could have been able to skip that step.
1432 Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
1433 then we can have max_length as dynamic property, which will be calculated during runtime and
1434 not during mysqli_fetch_field_direct() time.
1435 */
1436 if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1437 DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
1438 /* we have to initialized the rest to get the updated max length */
1439 if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
1440 break;
1441 }
1442 }
1443 DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr TSRMLS_CC));
1444 }
1445 } while (0);
1446
1447 DBG_RETURN(NULL);
1448 }
1449 /* }}} */
1450
1451
1452 /* {{{ mysqlnd_res::fetch_field */
1453 static const MYSQLND_FIELD *
MYSQLND_METHOD(mysqlnd_res,fetch_fields)1454 MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result TSRMLS_DC)
1455 {
1456 DBG_ENTER("mysqlnd_res::fetch_fields");
1457 do {
1458 if (result->meta) {
1459 if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
1460 /* we have to initialize the rest to get the updated max length */
1461 if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
1462 break;
1463 }
1464 }
1465 DBG_RETURN(result->meta->m->fetch_fields(result->meta TSRMLS_CC));
1466 }
1467 } while (0);
1468 DBG_RETURN(NULL);
1469 }
1470 /* }}} */
1471
1472
1473
1474 /* {{{ mysqlnd_res::field_seek */
1475 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_seek)1476 MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC)
1477 {
1478 MYSQLND_FIELD_OFFSET return_value = 0;
1479 if (result->meta) {
1480 return_value = result->meta->current_field;
1481 result->meta->current_field = field_offset;
1482 }
1483 return return_value;
1484 }
1485 /* }}} */
1486
1487
1488 /* {{{ mysqlnd_res::field_tell */
1489 static MYSQLND_FIELD_OFFSET
MYSQLND_METHOD(mysqlnd_res,field_tell)1490 MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result TSRMLS_DC)
1491 {
1492 return result->meta? result->meta->m->field_tell(result->meta TSRMLS_CC) : 0;
1493 }
1494 /* }}} */
1495
1496
1497 /* {{{ mysqlnd_res::fetch_into */
1498 static void
MYSQLND_METHOD(mysqlnd_res,fetch_into)1499 MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, unsigned int flags,
1500 zval *return_value,
1501 enum_mysqlnd_extension extension TSRMLS_DC ZEND_FILE_LINE_DC)
1502 {
1503 zend_bool fetched_anything;
1504
1505 DBG_ENTER("mysqlnd_res::fetch_into");
1506
1507 if (!result->m.fetch_row) {
1508 RETVAL_NULL();
1509 DBG_VOID_RETURN;
1510 }
1511 /*
1512 Hint Zend how many elements we will have in the hash. Thus it won't
1513 extend and rehash the hash constantly.
1514 */
1515 mysqlnd_array_init(return_value, mysqlnd_num_fields(result) * 2);
1516 if (FAIL == result->m.fetch_row(result, (void *)return_value, flags, &fetched_anything TSRMLS_CC)) {
1517 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading a row");
1518 zval_dtor(return_value);
1519 RETVAL_FALSE;
1520 } else if (fetched_anything == FALSE) {
1521 zval_dtor(return_value);
1522 switch (extension) {
1523 case MYSQLND_MYSQLI:
1524 RETVAL_NULL();
1525 break;
1526 case MYSQLND_MYSQL:
1527 RETVAL_FALSE;
1528 break;
1529 default:exit(0);
1530 }
1531 }
1532 /*
1533 return_value is IS_NULL for no more data and an array for data. Thus it's ok
1534 to return here.
1535 */
1536 DBG_VOID_RETURN;
1537 }
1538 /* }}} */
1539
1540
1541 /* {{{ mysqlnd_res::fetch_row_c */
1542 static MYSQLND_ROW_C
MYSQLND_METHOD(mysqlnd_res,fetch_row_c)1543 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result TSRMLS_DC)
1544 {
1545 MYSQLND_ROW_C ret = NULL;
1546 DBG_ENTER("mysqlnd_res::fetch_row_c");
1547
1548 if (result->m.fetch_row) {
1549 if (result->m.fetch_row == result->m.fetch_row_normal_buffered) {
1550 DBG_RETURN(mysqlnd_fetch_row_buffered_c(result TSRMLS_CC));
1551 } else if (result->m.fetch_row == result->m.fetch_row_normal_unbuffered) {
1552 DBG_RETURN(mysqlnd_fetch_row_unbuffered_c(result TSRMLS_CC));
1553 } else {
1554 php_error_docref(NULL TSRMLS_CC, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers");
1555 }
1556 }
1557 DBG_RETURN(ret);
1558 }
1559 /* }}} */
1560
1561
1562 /* {{{ mysqlnd_res::fetch_all */
1563 static void
MYSQLND_METHOD(mysqlnd_res,fetch_all)1564 MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC)
1565 {
1566 zval *row;
1567 ulong i = 0;
1568 MYSQLND_RES_BUFFERED *set = result->stored_data;
1569
1570 DBG_ENTER("mysqlnd_res::fetch_all");
1571
1572 if ((!result->unbuf && !set)) {
1573 php_error_docref(NULL TSRMLS_CC, E_WARNING, "fetch_all can be used only with buffered sets");
1574 if (result->conn) {
1575 SET_CLIENT_ERROR(*result->conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "fetch_all can be used only with buffered sets");
1576 }
1577 RETVAL_NULL();
1578 DBG_VOID_RETURN;
1579 }
1580
1581 /* 4 is a magic value. The cast is safe, if larger then the array will be later extended - no big deal :) */
1582 mysqlnd_array_init(return_value, set? (unsigned int) set->row_count : 4);
1583
1584 do {
1585 MAKE_STD_ZVAL(row);
1586 mysqlnd_fetch_into(result, flags, row, MYSQLND_MYSQLI);
1587 if (Z_TYPE_P(row) != IS_ARRAY) {
1588 zval_ptr_dtor(&row);
1589 break;
1590 }
1591 add_index_zval(return_value, i++, row);
1592 } while (1);
1593
1594 DBG_VOID_RETURN;
1595 }
1596 /* }}} */
1597
1598
1599 /* {{{ mysqlnd_res::fetch_field_data */
1600 static void
MYSQLND_METHOD(mysqlnd_res,fetch_field_data)1601 MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, unsigned int offset, zval *return_value TSRMLS_DC)
1602 {
1603 zval row;
1604 zval **entry;
1605 unsigned int i = 0;
1606
1607 DBG_ENTER("mysqlnd_res::fetch_field_data");
1608 DBG_INF_FMT("offset=%u", offset);
1609
1610 if (!result->m.fetch_row) {
1611 RETVAL_NULL();
1612 DBG_VOID_RETURN;
1613 }
1614 /*
1615 Hint Zend how many elements we will have in the hash. Thus it won't
1616 extend and rehash the hash constantly.
1617 */
1618 INIT_PZVAL(&row);
1619 mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, &row, MYSQLND_MYSQL);
1620 if (Z_TYPE(row) != IS_ARRAY) {
1621 zval_dtor(&row);
1622 RETVAL_NULL();
1623 DBG_VOID_RETURN;
1624 }
1625 zend_hash_internal_pointer_reset(Z_ARRVAL(row));
1626 while (i++ < offset) {
1627 zend_hash_move_forward(Z_ARRVAL(row));
1628 zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry);
1629 }
1630
1631 zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry);
1632
1633 *return_value = **entry;
1634 zval_copy_ctor(return_value);
1635 Z_SET_REFCOUNT_P(return_value, 1);
1636 zval_dtor(&row);
1637
1638 DBG_VOID_RETURN;
1639 }
1640 /* }}} */
1641
1642
1643 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
1644 NULL, /* fetch_row */
1645 mysqlnd_fetch_row_buffered,
1646 mysqlnd_fetch_row_unbuffered,
1647 MYSQLND_METHOD(mysqlnd_res, use_result),
1648 MYSQLND_METHOD(mysqlnd_res, store_result),
1649 MYSQLND_METHOD(mysqlnd_res, fetch_into),
1650 MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
1651 MYSQLND_METHOD(mysqlnd_res, fetch_all),
1652 MYSQLND_METHOD(mysqlnd_res, fetch_field_data),
1653 MYSQLND_METHOD(mysqlnd_res, num_rows),
1654 MYSQLND_METHOD(mysqlnd_res, num_fields),
1655 MYSQLND_METHOD(mysqlnd_res, skip_result),
1656 MYSQLND_METHOD(mysqlnd_res, data_seek),
1657 MYSQLND_METHOD(mysqlnd_res, field_seek),
1658 MYSQLND_METHOD(mysqlnd_res, field_tell),
1659 MYSQLND_METHOD(mysqlnd_res, fetch_field),
1660 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
1661 MYSQLND_METHOD(mysqlnd_res, fetch_fields),
1662 MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
1663 NULL, /* fetch_lengths */
1664 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
1665 MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest),
1666 MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
1667 MYSQLND_METHOD(mysqlnd_res, free_result),
1668
1669 mysqlnd_internal_free_result, /* free_result_internal */
1670 mysqlnd_internal_free_result_contents, /* free_result_contents */
1671 MYSQLND_METHOD(mysqlnd_res, free_buffered_data),
1672 MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data),
1673
1674 NULL /* row_decoder */,
1675 mysqlnd_result_meta_init
1676 MYSQLND_CLASS_METHODS_END;
1677
1678
1679 /* {{{ mysqlnd_result_init */
1680 PHPAPI MYSQLND_RES *
mysqlnd_result_init(unsigned int field_count,zend_bool persistent TSRMLS_DC)1681 mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC)
1682 {
1683 size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
1684 MYSQLND_RES *ret = mnd_pecalloc(1, alloc_size, persistent);
1685
1686 DBG_ENTER("mysqlnd_result_init");
1687
1688 if (!ret) {
1689 DBG_RETURN(NULL);
1690 }
1691
1692 ret->persistent = persistent;
1693 ret->field_count = field_count;
1694 ret->m = *mysqlnd_result_get_methods();
1695
1696 DBG_RETURN(ret);
1697 }
1698 /* }}} */
1699
1700
1701 /*
1702 * Local variables:
1703 * tab-width: 4
1704 * c-basic-offset: 4
1705 * End:
1706 * vim600: noet sw=4 ts=4 fdm=marker
1707 * vim<600: noet sw=4 ts=4
1708 */
1709