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