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