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