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