Back to index

php5  5.3.10
mysqlnd_result.c
Go to the documentation of this file.
00001 /*
00002   +----------------------------------------------------------------------+
00003   | PHP Version 5                                                        |
00004   +----------------------------------------------------------------------+
00005   | Copyright (c) 2006-2012 The PHP Group                                |
00006   +----------------------------------------------------------------------+
00007   | This source file is subject to version 3.01 of the PHP license,      |
00008   | that is bundled with this package in the file LICENSE, and is        |
00009   | available through the world-wide-web at the following url:           |
00010   | http://www.php.net/license/3_01.txt                                  |
00011   | If you did not receive a copy of the PHP license and are unable to   |
00012   | obtain it through the world-wide-web, please send a note to          |
00013   | license@php.net so we can mail you a copy immediately.               |
00014   +----------------------------------------------------------------------+
00015   | Authors: Georg Richter <georg@mysql.com>                             |
00016   |          Andrey Hristov <andrey@mysql.com>                           |
00017   |          Ulf Wendel <uwendel@mysql.com>                              |
00018   +----------------------------------------------------------------------+
00019 */
00020 
00021 /* $Id: mysqlnd_result.c 321634 2012-01-01 13:15:04Z felipe $ */
00022 #include "php.h"
00023 #include "mysqlnd.h"
00024 #include "mysqlnd_wireprotocol.h"
00025 #include "mysqlnd_block_alloc.h"
00026 #include "mysqlnd_priv.h"
00027 #include "mysqlnd_result.h"
00028 #include "mysqlnd_result_meta.h"
00029 #include "mysqlnd_statistics.h"
00030 #include "mysqlnd_debug.h"
00031 
00032 #define MYSQLND_SILENT
00033 
00034 
00035 /* {{{ mysqlnd_res::initialize_result_set_rest */
00036 static enum_func_status
00037 MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest)(MYSQLND_RES * const result TSRMLS_DC)
00038 {
00039        unsigned int i;
00040        zval **data_cursor = result->stored_data? result->stored_data->data:NULL;
00041        zval **data_begin = result->stored_data? result->stored_data->data:NULL;
00042        unsigned int field_count = result->meta? result->meta->field_count : 0;
00043        uint64_t row_count = result->stored_data? result->stored_data->row_count:0;
00044        enum_func_status ret = PASS;
00045        DBG_ENTER("mysqlnd_res::initialize_result_set_rest");
00046 
00047        if (!data_cursor || row_count == result->stored_data->initialized_rows) {
00048               DBG_RETURN(ret);
00049        }
00050        while ((data_cursor - data_begin) < (int)(row_count * field_count)) {
00051               if (NULL == data_cursor[0]) {
00052                      enum_func_status rc = result->m.row_decoder(
00053                                                                result->stored_data->row_buffers[(data_cursor - data_begin) / field_count],
00054                                                                data_cursor,
00055                                                                result->meta->field_count,
00056                                                                result->meta->fields,
00057                                                                result->stored_data->persistent,
00058                                                                result->conn->options.numeric_and_datetime_as_unicode,
00059                                                                result->conn->options.int_and_float_native,
00060                                                                result->conn->stats TSRMLS_CC);
00061                      if (rc != PASS) {
00062                             ret = FAIL;
00063                             break;
00064                      }
00065                      result->stored_data->initialized_rows++;
00066                      for (i = 0; i < result->field_count; i++) {
00067                             /*
00068                               NULL fields are 0 length, 0 is not more than 0
00069                               String of zero size, definitely can't be the next max_length.
00070                               Thus for NULL and zero-length we are quite efficient.
00071                             */
00072                             if (Z_TYPE_P(data_cursor[i]) >= IS_STRING) {
00073                                    unsigned long len = Z_STRLEN_P(data_cursor[i]);
00074                                    if (result->meta->fields[i].max_length < len) {
00075                                           result->meta->fields[i].max_length = len;
00076                                    }
00077                             }
00078                      }
00079               }
00080               data_cursor += field_count;
00081        }
00082        DBG_RETURN(ret);
00083 }
00084 /* }}} */
00085 
00086 
00087 /* {{{ mysqlnd_rset_zval_ptr_dtor */
00088 static void
00089 mysqlnd_rset_zval_ptr_dtor(zval **zv, enum_mysqlnd_res_type type, zend_bool * copy_ctor_called TSRMLS_DC)
00090 {
00091        DBG_ENTER("mysqlnd_rset_zval_ptr_dtor");
00092        if (!zv || !*zv) {
00093               *copy_ctor_called = FALSE;
00094               DBG_ERR_FMT("zv was NULL");
00095               DBG_VOID_RETURN;
00096        }
00097        /*
00098          This zval is not from the cache block.
00099          Thus the refcount is -1 than of a zval from the cache,
00100          because the zvals from the cache are owned by it.
00101        */
00102        if (type == MYSQLND_RES_PS_BUF || type == MYSQLND_RES_PS_UNBUF) {
00103               *copy_ctor_called = FALSE;
00104               ; /* do nothing, zval_ptr_dtor will do the job*/
00105        } else if (Z_REFCOUNT_PP(zv) > 1) {
00106               /*
00107                 Not a prepared statement, then we have to
00108                 call copy_ctor and then zval_ptr_dtor()
00109 
00110                 In Unicode mode the destruction  of the zvals should not call
00111                 zval_copy_ctor() because then we will leak.
00112                 I suppose we can use UG(unicode) in mysqlnd.c when freeing a result set
00113                 to check if we need to call copy_ctor().
00114 
00115                 If the type is IS_UNICODE, which can happen with PHP6, then we don't
00116                 need to copy_ctor, as the data doesn't point to our internal buffers.
00117                 If it's string (in PHP5 always) and in PHP6 if data is binary, then
00118                 it still points to internal buffers and has to be copied.
00119               */
00120               if (Z_TYPE_PP(zv) == IS_STRING) {
00121                      zval_copy_ctor(*zv);
00122               }
00123               *copy_ctor_called = TRUE;
00124        } else {
00125               /*
00126                 noone but us point to this, so we can safely ZVAL_NULL the zval,
00127                 so Zend does not try to free what the zval points to - which is
00128                 in result set buffers
00129               */
00130               *copy_ctor_called = FALSE;
00131               if (Z_TYPE_PP(zv) == IS_STRING) {
00132                      ZVAL_NULL(*zv);
00133               }
00134        }
00135        zval_ptr_dtor(zv);
00136        DBG_VOID_RETURN;
00137 }
00138 /* }}} */
00139 
00140 
00141 /* {{{ mysqlnd_res::unbuffered_free_last_data */
00142 static void
00143 MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data)(MYSQLND_RES * result TSRMLS_DC)
00144 {
00145        MYSQLND_RES_UNBUFFERED *unbuf = result->unbuf;
00146 
00147        DBG_ENTER("mysqlnd_res::unbuffered_free_last_data");
00148 
00149        if (!unbuf) {
00150               DBG_VOID_RETURN;
00151        }
00152 
00153        DBG_INF_FMT("last_row_data=%p", unbuf->last_row_data);
00154        if (unbuf->last_row_data) {
00155               unsigned int i, ctor_called_count = 0;
00156               zend_bool copy_ctor_called;
00157               MYSQLND_STATS *global_stats = result->conn? result->conn->stats:NULL;
00158 
00159               DBG_INF_FMT("%u columns to free", result->field_count);
00160               for (i = 0; i < result->field_count; i++) {
00161                      mysqlnd_rset_zval_ptr_dtor(&(unbuf->last_row_data[i]), result->type, &copy_ctor_called TSRMLS_CC);
00162                      if (copy_ctor_called) {
00163                             ++ctor_called_count;
00164                      }
00165               }
00166               DBG_INF_FMT("copy_ctor_called_count=%u", ctor_called_count);
00167               /* By using value3 macros we hold a mutex only once, there is no value2 */
00168               MYSQLND_INC_CONN_STATISTIC_W_VALUE2(global_stats,
00169                                                                              STAT_COPY_ON_WRITE_PERFORMED,
00170                                                                              ctor_called_count,
00171                                                                              STAT_COPY_ON_WRITE_SAVED,
00172                                                                              result->field_count - ctor_called_count);
00173               /* Free last row's zvals */
00174               mnd_efree(unbuf->last_row_data);
00175               unbuf->last_row_data = NULL;
00176        }
00177        if (unbuf->last_row_buffer) {
00178               DBG_INF("Freeing last row buffer");
00179               /* Nothing points to this buffer now, free it */
00180               unbuf->last_row_buffer->free_chunk(unbuf->last_row_buffer TSRMLS_CC);
00181               unbuf->last_row_buffer = NULL;
00182        }
00183 
00184        DBG_VOID_RETURN;
00185 }
00186 /* }}} */
00187 
00188 
00189 /* {{{ mysqlnd_res::free_buffered_data */
00190 static void
00191 MYSQLND_METHOD(mysqlnd_res, free_buffered_data)(MYSQLND_RES * result TSRMLS_DC)
00192 {
00193        MYSQLND_RES_BUFFERED *set = result->stored_data;
00194        unsigned int field_count = result->field_count;
00195        int64_t row;
00196 
00197        DBG_ENTER("mysqlnd_res::free_buffered_data");
00198        DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count);
00199 
00200        DBG_INF("Freeing data & row_buffer");
00201        if (set->data) {
00202               unsigned int copy_on_write_performed = 0;
00203               unsigned int copy_on_write_saved = 0;
00204 
00205               DBG_INF_FMT("before: real_usage=%lu  usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC));
00206               for (row = set->row_count - 1; row >= 0; row--) {
00207                      zval **current_row = set->data + row * field_count;
00208                      MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row];
00209                      int64_t col;
00210 
00211                      if (current_row != NULL) {
00212                             for (col = field_count - 1; col >= 0; --col) {
00213                                    if (current_row[col]) {
00214                                           zend_bool copy_ctor_called;
00215                                           mysqlnd_rset_zval_ptr_dtor(&(current_row[col]), result->type, &copy_ctor_called TSRMLS_CC);
00216 #if MYSQLND_DEBUG_MEMORY
00217                                           DBG_INF_FMT("Copy_ctor_called=%u", copy_ctor_called);
00218 #endif
00219                                           if (copy_ctor_called) {
00220                                                  ++copy_on_write_performed;
00221                                           } else {
00222                                                  ++copy_on_write_saved;
00223                                           }
00224                                    }
00225                             }
00226                      }
00227 #if MYSQLND_DEBUG_MEMORY
00228                      DBG_INF("Freeing current_row & current_buffer");
00229 #endif
00230                      current_buffer->free_chunk(current_buffer TSRMLS_CC);
00231               }
00232 
00233               MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_COPY_ON_WRITE_PERFORMED, copy_on_write_performed,
00234                                                                                STAT_COPY_ON_WRITE_SAVED, copy_on_write_saved);
00235               mnd_pefree(set->data, set->persistent);
00236               set->data = NULL;
00237        }
00238 
00239        if (set->row_buffers) {
00240               mnd_pefree(set->row_buffers, set->persistent);
00241               set->row_buffers     = NULL;
00242        }
00243        set->data_cursor = NULL;
00244        set->row_count       = 0;
00245 
00246        DBG_INF("Freeing set");
00247        mnd_pefree(set, set->persistent);
00248 
00249        DBG_INF_FMT("after: real_usage=%lu  usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC));
00250        DBG_VOID_RETURN;
00251 }
00252 /* }}} */
00253 
00254 
00255 /* {{{ mysqlnd_res::free_result_buffers */
00256 static void
00257 MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES * result TSRMLS_DC)
00258 {
00259        DBG_ENTER("mysqlnd_res::free_result_buffers");
00260        DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown"));
00261 
00262        if (result->unbuf) {
00263               result->m.unbuffered_free_last_data(result TSRMLS_CC);
00264               mnd_efree(result->unbuf);
00265               result->unbuf = NULL;
00266        } else if (result->stored_data) {
00267               result->m.free_buffered_data(result TSRMLS_CC);
00268               result->stored_data = NULL;
00269        }
00270 
00271        if (result->lengths) {
00272               mnd_efree(result->lengths);
00273               result->lengths = NULL;
00274        }
00275 
00276        if (result->row_packet) {
00277               DBG_INF("Freeing packet");
00278               PACKET_FREE(result->row_packet);
00279               result->row_packet = NULL;
00280        }
00281 
00282        if (result->result_set_memory_pool) {
00283               DBG_INF("Freeing memory pool");
00284               mysqlnd_mempool_destroy(result->result_set_memory_pool TSRMLS_CC);
00285               result->result_set_memory_pool = NULL;
00286        }
00287 
00288        DBG_VOID_RETURN;
00289 }
00290 /* }}} */
00291 
00292 
00293 /* {{{ mysqlnd_internal_free_result_contents */
00294 static
00295 void mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC)
00296 {
00297        DBG_ENTER("mysqlnd_internal_free_result_contents");
00298 
00299        result->m.free_result_buffers(result TSRMLS_CC);
00300 
00301        if (result->meta) {
00302               result->meta->m->free_metadata(result->meta TSRMLS_CC);
00303               result->meta = NULL;
00304        }
00305 
00306        DBG_VOID_RETURN;
00307 }
00308 /* }}} */
00309 
00310 
00311 /* {{{ mysqlnd_internal_free_result */
00312 static
00313 void mysqlnd_internal_free_result(MYSQLND_RES * result TSRMLS_DC)
00314 {
00315        DBG_ENTER("mysqlnd_internal_free_result");
00316        result->m.free_result_contents(result TSRMLS_CC);
00317 
00318        if (result->conn) {
00319               result->conn->m->free_reference(result->conn TSRMLS_CC);
00320               result->conn = NULL;
00321        }
00322 
00323        mnd_pefree(result, result->persistent);
00324 
00325        DBG_VOID_RETURN;
00326 }
00327 /* }}} */
00328 
00329 
00330 /* {{{ mysqlnd_res::read_result_metadata */
00331 static enum_func_status
00332 MYSQLND_METHOD(mysqlnd_res, read_result_metadata)(MYSQLND_RES * result, MYSQLND *conn TSRMLS_DC)
00333 {
00334        DBG_ENTER("mysqlnd_res::read_result_metadata");
00335 
00336        /*
00337          Make it safe to call it repeatedly for PS -
00338          better free and allocate a new because the number of field might change 
00339          (select *) with altered table. Also for statements which skip the PS
00340          infrastructure!
00341        */
00342        if (result->meta) {
00343               result->meta->m->free_metadata(result->meta TSRMLS_CC);
00344               result->meta = NULL;
00345        }
00346 
00347        result->meta = result->m.result_meta_init(result->field_count, result->persistent TSRMLS_CC);
00348        if (!result->meta) {
00349               SET_OOM_ERROR(conn->error_info);
00350               DBG_RETURN(FAIL);
00351        }
00352 
00353        /* 1. Read all fields metadata */
00354 
00355        /* It's safe to reread without freeing */
00356        if (FAIL == result->meta->m->read_metadata(result->meta, conn TSRMLS_CC)) {
00357               result->m.free_result_contents(result TSRMLS_CC);
00358               DBG_RETURN(FAIL);
00359        }
00360        /* COM_FIELD_LIST is broken and has premature EOF, thus we need to hack here and in mysqlnd_res_meta.c */
00361        result->field_count = result->meta->field_count;
00362 
00363        /*
00364          2. Follows an EOF packet, which the client of mysqlnd_read_result_metadata()
00365             should consume.
00366          3. If there is a result set, it follows. The last packet will have 'eof' set
00367             If PS, then no result set follows.
00368        */
00369 
00370        DBG_RETURN(PASS);
00371 }
00372 /* }}} */
00373 
00374 
00375 /* {{{ mysqlnd_query_read_result_set_header */
00376 enum_func_status
00377 mysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT * s TSRMLS_DC)
00378 {
00379        MYSQLND_STMT_DATA * stmt = s ? s->data:NULL;
00380        enum_func_status ret;
00381        MYSQLND_PACKET_RSET_HEADER * rset_header = NULL;
00382        MYSQLND_PACKET_EOF * fields_eof = NULL;
00383 
00384        DBG_ENTER("mysqlnd_query_read_result_set_header");
00385        DBG_INF_FMT("stmt=%lu", stmt? stmt->stmt_id:0);
00386 
00387        ret = FAIL;
00388        do {
00389               rset_header = conn->protocol->m.get_rset_header_packet(conn->protocol, FALSE TSRMLS_CC);
00390               if (!rset_header) {
00391                      SET_OOM_ERROR(conn->error_info);
00392                      ret = FAIL;
00393                      break;
00394               }
00395 
00396               SET_ERROR_AFF_ROWS(conn);
00397 
00398               if (FAIL == (ret = PACKET_READ(rset_header, conn))) {
00399                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error reading result set's header");
00400                      break;
00401               }
00402 
00403               if (rset_header->error_info.error_no) {
00404                      /*
00405                        Cover a protocol design error: error packet does not
00406                        contain the server status. Therefore, the client has no way
00407                        to find out whether there are more result sets of
00408                        a multiple-result-set statement pending. Luckily, in 5.0 an
00409                        error always aborts execution of a statement, wherever it is
00410                        a multi-statement or a stored procedure, so it should be
00411                        safe to unconditionally turn off the flag here.
00412                      */
00413                      conn->upsert_status.server_status &= ~SERVER_MORE_RESULTS_EXISTS;
00414                      /*
00415                        This will copy the error code and the messages, as they
00416                        are buffers in the struct
00417                      */
00418                      conn->error_info = rset_header->error_info;
00419                      ret = FAIL;
00420                      DBG_ERR_FMT("error=%s", rset_header->error_info.error);
00421                      /* Return back from CONN_QUERY_SENT */
00422                      CONN_SET_STATE(conn, CONN_READY);
00423                      break;
00424               }
00425               conn->error_info.error_no = 0;
00426 
00427               switch (rset_header->field_count) {
00428                      case MYSQLND_NULL_LENGTH: { /* LOAD DATA LOCAL INFILE */
00429                             zend_bool is_warning;
00430                             DBG_INF("LOAD DATA");
00431                             conn->last_query_type = QUERY_LOAD_LOCAL;
00432                             conn->field_count = 0; /* overwrite previous value, or the last value could be used and lead to bug#53503 */
00433                             CONN_SET_STATE(conn, CONN_SENDING_LOAD_DATA);
00434                             ret = mysqlnd_handle_local_infile(conn, rset_header->info_or_local_file, &is_warning TSRMLS_CC);
00435                             CONN_SET_STATE(conn,  (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT);
00436                             MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
00437                             break;
00438                      }
00439                      case 0:                            /* UPSERT */
00440                             DBG_INF("UPSERT");
00441                             conn->last_query_type = QUERY_UPSERT;
00442                             conn->field_count = rset_header->field_count;
00443                             conn->upsert_status.warning_count = rset_header->warning_count;
00444                             conn->upsert_status.server_status = rset_header->server_status;
00445                             conn->upsert_status.affected_rows = rset_header->affected_rows;
00446                             conn->upsert_status.last_insert_id = rset_header->last_insert_id;
00447                             SET_NEW_MESSAGE(conn->last_message, conn->last_message_len,
00448                                                         rset_header->info_or_local_file, rset_header->info_or_local_file_len,
00449                                                         conn->persistent);
00450                             /* Result set can follow UPSERT statement, check server_status */
00451                             if (conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
00452                                    CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
00453                             } else {
00454                                    CONN_SET_STATE(conn, CONN_READY);
00455                             }
00456                             ret = PASS;
00457                             MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY);
00458                             break;
00459                      default: do {               /* Result set */
00460                             MYSQLND_RES * result;
00461                             enum_mysqlnd_collected_stats statistic = STAT_LAST;
00462 
00463                             DBG_INF("Result set pending");
00464                             SET_EMPTY_MESSAGE(conn->last_message, conn->last_message_len, conn->persistent);
00465 
00466                             MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_RSET_QUERY);
00467                             memset(&conn->upsert_status, 0, sizeof(conn->upsert_status));
00468                             /* restore after zeroing */
00469                             SET_ERROR_AFF_ROWS(conn);
00470 
00471                             conn->last_query_type = QUERY_SELECT;
00472                             CONN_SET_STATE(conn, CONN_FETCHING_DATA);
00473                             /* PS has already allocated it */
00474                             conn->field_count = rset_header->field_count;
00475                             if (!stmt) {
00476                                    result = conn->current_result = conn->m->result_init(rset_header->field_count, conn->persistent TSRMLS_CC);
00477                             } else {
00478                                    if (!stmt->result) {
00479                                           DBG_INF("This is 'SHOW'/'EXPLAIN'-like query.");
00480                                           /*
00481                                             This is 'SHOW'/'EXPLAIN'-like query. Current implementation of
00482                                             prepared statements can't send result set metadata for these queries
00483                                             on prepare stage. Read it now.
00484                                           */
00485                                           result = stmt->result = conn->m->result_init(rset_header->field_count, stmt->persistent TSRMLS_CC);
00486                                    } else {
00487                                           /*
00488                                             Update result set metadata if it for some reason changed between
00489                                             prepare and execute, i.e.:
00490                                             - in case of 'SELECT ?' we don't know column type unless data was
00491                                                  supplied to mysql_stmt_execute, so updated column type is sent
00492                                                  now.
00493                                             - if data dictionary changed between prepare and execute, for
00494                                                  example a table used in the query was altered.
00495                                             Note, that now (4.1.3) we always send metadata in reply to
00496                                             COM_STMT_EXECUTE (even if it is not necessary), so either this or
00497                                             previous branch always works.
00498                                           */
00499                                    }
00500                                    result = stmt->result;
00501                             }
00502                             if (!result) {
00503                                    SET_OOM_ERROR(conn->error_info);
00504                                    ret = FAIL;
00505                                    break;
00506                             }
00507 
00508                             if (FAIL == (ret = result->m.read_result_metadata(result, conn TSRMLS_CC))) {
00509                                    /* For PS, we leave them in Prepared state */
00510                                    if (!stmt && conn->current_result) {
00511                                           mnd_efree(conn->current_result);
00512                                           conn->current_result = NULL;
00513                                    }
00514                                    DBG_ERR("Error ocurred while reading metadata");
00515                                    break;
00516                             }
00517 
00518                             /* Check for SERVER_STATUS_MORE_RESULTS if needed */
00519                             fields_eof = conn->protocol->m.get_eof_packet(conn->protocol, FALSE TSRMLS_CC);
00520                             if (!fields_eof) {
00521                                    SET_OOM_ERROR(conn->error_info);
00522                                    ret = FAIL;
00523                                    break;
00524                             }
00525                             if (FAIL == (ret = PACKET_READ(fields_eof, conn))) {
00526                                    DBG_ERR("Error ocurred while reading the EOF packet");
00527                                    result->m.free_result_contents(result TSRMLS_CC);
00528                                    mnd_efree(result);
00529                                    if (!stmt) {
00530                                           conn->current_result = NULL;
00531                                    } else {
00532                                           stmt->result = NULL;
00533                                           memset(stmt, 0, sizeof(MYSQLND_STMT));
00534                                           stmt->state = MYSQLND_STMT_INITTED;
00535                                    }
00536                             } else {
00537                                    unsigned int to_log = MYSQLND_G(log_mask);
00538                                    to_log &= fields_eof->server_status;
00539                                    DBG_INF_FMT("warnings=%u server_status=%u", fields_eof->warning_count, fields_eof->server_status);
00540                                    conn->upsert_status.warning_count = fields_eof->warning_count;
00541                                    /*
00542                                      If SERVER_MORE_RESULTS_EXISTS is set then this is either MULTI_QUERY or a CALL()
00543                                      The first packet after sending the query/com_execute has the bit set only
00544                                      in this cases. Not sure why it's a needed but it marks that the whole stream
00545                                      will include many result sets. What actually matters are the bits set at the end
00546                                      of every result set (the EOF packet).
00547                                    */
00548                                    conn->upsert_status.server_status = fields_eof->server_status;
00549                                    if (fields_eof->server_status & SERVER_QUERY_NO_GOOD_INDEX_USED) {
00550                                           statistic = STAT_BAD_INDEX_USED;
00551                                    } else if (fields_eof->server_status & SERVER_QUERY_NO_INDEX_USED) {
00552                                           statistic = STAT_NO_INDEX_USED;
00553                                    } else if (fields_eof->server_status & SERVER_QUERY_WAS_SLOW) {
00554                                           statistic = STAT_QUERY_WAS_SLOW;
00555                                    }
00556                                    if (to_log) {
00557 #if A0
00558                                           char *backtrace = mysqlnd_get_backtrace(TSRMLS_C);
00559                                           php_log_err(backtrace TSRMLS_CC);
00560                                           efree(backtrace);
00561 #endif
00562                                    }
00563                                    MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
00564                             }
00565                      } while (0);
00566                      PACKET_FREE(fields_eof);
00567                      break; /* switch break */
00568               }
00569        } while (0);
00570        PACKET_FREE(rset_header);
00571 
00572        DBG_INF(ret == PASS? "PASS":"FAIL");
00573        DBG_RETURN(ret);
00574 }
00575 /* }}} */
00576 
00577 
00578 /* {{{ mysqlnd_fetch_lengths_buffered */
00579 /*
00580   Do lazy initialization for buffered results. As PHP strings have
00581   length inside, this function makes not much sense in the context
00582   of PHP, to be called as separate function. But let's have it for
00583   completeness.
00584 */
00585 static unsigned long *
00586 mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result TSRMLS_DC)
00587 {
00588        unsigned int i;
00589        zval **previous_row;
00590        MYSQLND_RES_BUFFERED *set = result->stored_data;
00591 
00592        /*
00593          If:
00594          - unbuffered result
00595          - first row has not been read
00596          - last_row has been read
00597        */
00598        if (set->data_cursor == NULL ||
00599               set->data_cursor == set->data ||
00600               ((set->data_cursor - set->data) > (set->row_count * result->meta->field_count) ))
00601        {
00602               return NULL;/* No rows or no more rows */
00603        }
00604 
00605        previous_row = set->data_cursor - result->meta->field_count;
00606        for (i = 0; i < result->meta->field_count; i++) {
00607               result->lengths[i] = (Z_TYPE_P(previous_row[i]) == IS_NULL)? 0:Z_STRLEN_P(previous_row[i]);
00608        }
00609 
00610        return result->lengths;
00611 }
00612 /* }}} */
00613 
00614 
00615 /* {{{ mysqlnd_fetch_lengths_unbuffered */
00616 static unsigned long *
00617 mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result TSRMLS_DC)
00618 {
00619        /* simulate output of libmysql */
00620        return (!result->unbuf || result->unbuf->last_row_data || result->unbuf->eof_reached)? result->lengths:NULL;
00621 }
00622 /* }}} */
00623 
00624 
00625 /* {{{ mysqlnd_res::fetch_lengths */
00626 PHPAPI unsigned long * _mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC)
00627 {
00628        return result->m.fetch_lengths? result->m.fetch_lengths(result TSRMLS_CC) : NULL;
00629 }
00630 /* }}} */
00631 
00632 
00633 /* {{{ mysqlnd_fetch_row_unbuffered_c */
00634 static MYSQLND_ROW_C
00635 mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC)
00636 {
00637        enum_func_status     ret;
00638        MYSQLND_ROW_C        retrow = NULL;
00639        unsigned int         i,
00640                                           field_count = result->field_count;
00641        MYSQLND_PACKET_ROW   *row_packet = result->row_packet;
00642        unsigned long        *lengths = result->lengths;
00643 
00644        DBG_ENTER("mysqlnd_fetch_row_unbuffered_c");
00645 
00646        if (result->unbuf->eof_reached) {
00647               /* No more rows obviously */
00648               DBG_RETURN(retrow);
00649        }
00650        if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
00651               SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
00652                                            UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); 
00653               DBG_RETURN(retrow);
00654        }
00655        if (!row_packet) {
00656               /* Not fully initialized object that is being cleaned up */
00657               DBG_RETURN(retrow);
00658        }
00659        /* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
00660        row_packet->skip_extraction = FALSE;
00661 
00662        /*
00663          If we skip rows (row == NULL) we have to
00664          result->m.unbuffered_free_last_data() before it. The function returns always true.
00665        */
00666        if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
00667               result->unbuf->row_count++;
00668 
00669               result->m.unbuffered_free_last_data(result TSRMLS_CC);
00670 
00671               result->unbuf->last_row_data = row_packet->fields;
00672               result->unbuf->last_row_buffer = row_packet->row_buffer;
00673               row_packet->fields = NULL;
00674               row_packet->row_buffer = NULL;
00675 
00676               MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
00677 
00678               if (!row_packet->skip_extraction) {
00679                      MYSQLND_FIELD *field = result->meta->fields;
00680                      struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
00681 
00682                      enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer,
00683                                                                         result->unbuf->last_row_data,
00684                                                                         row_packet->field_count,
00685                                                                         row_packet->fields_metadata,
00686                                                                         FALSE,
00687                                                                         result->conn->options.numeric_and_datetime_as_unicode,
00688                                                                         result->conn->options.int_and_float_native,
00689                                                                         result->conn->stats TSRMLS_CC);
00690                      if (PASS != rc) {
00691                             DBG_RETURN(retrow);
00692                      }
00693 
00694                      retrow = mnd_malloc(result->field_count * sizeof(char *));
00695                      if (retrow) {
00696                             for (i = 0; i < field_count; i++, field++, hash_key++) {
00697                                    zval *data = result->unbuf->last_row_data[i];
00698                                    unsigned int len;
00699 
00700                                    if (Z_TYPE_P(data) != IS_NULL) {
00701                                           convert_to_string(data);
00702                                           retrow[i] = Z_STRVAL_P(data);
00703                                           len = Z_STRLEN_P(data);
00704                                    } else {
00705                                           retrow[i] = NULL;
00706                                           len = 0;
00707                                    }
00708 
00709                                    if (lengths) {
00710                                           lengths[i] = len;
00711                                    }
00712 
00713                                    if (field->max_length < len) {
00714                                           field->max_length = len;
00715                                    }
00716                             }
00717                      } else {
00718                             SET_OOM_ERROR(result->conn->error_info);
00719                      }
00720               }
00721        } else if (ret == FAIL) {
00722               if (row_packet->error_info.error_no) {
00723                      result->conn->error_info = row_packet->error_info;
00724                      DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
00725               }
00726               CONN_SET_STATE(result->conn, CONN_READY);
00727               result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
00728        } else if (row_packet->eof) {
00729               /* Mark the connection as usable again */
00730               DBG_INF_FMT("warningss=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
00731               result->unbuf->eof_reached = TRUE;
00732               result->conn->upsert_status.warning_count = row_packet->warning_count;
00733               result->conn->upsert_status.server_status = row_packet->server_status;
00734               /*
00735                 result->row_packet will be cleaned when
00736                 destroying the result object
00737               */
00738               if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
00739                      CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
00740               } else {
00741                      CONN_SET_STATE(result->conn, CONN_READY);
00742               }
00743               result->m.unbuffered_free_last_data(result TSRMLS_CC);
00744        }
00745 
00746        DBG_RETURN(retrow);
00747 }
00748 /* }}} */
00749 
00750 
00751 /* {{{ mysqlnd_fetch_row_unbuffered */
00752 static enum_func_status
00753 mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
00754 {
00755        enum_func_status     ret;
00756        zval                        *row = (zval *) param;
00757        MYSQLND_PACKET_ROW   *row_packet = result->row_packet;
00758 
00759        DBG_ENTER("mysqlnd_fetch_row_unbuffered");
00760        DBG_INF_FMT("flags=%u", flags);
00761 
00762        *fetched_anything = FALSE;
00763        if (result->unbuf->eof_reached) {
00764               /* No more rows obviously */
00765               DBG_RETURN(PASS);
00766        }
00767        if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
00768               SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
00769               DBG_RETURN(FAIL);
00770        }
00771        if (!row_packet) {
00772               /* Not fully initialized object that is being cleaned up */
00773               DBG_RETURN(FAIL);
00774        }
00775        /* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */
00776        row_packet->skip_extraction = row? FALSE:TRUE;
00777 
00778        /*
00779          If we skip rows (row == NULL) we have to
00780          result->m.unbuffered_free_last_data() before it. The function returns always true.
00781        */
00782        if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
00783               result->m.unbuffered_free_last_data(result TSRMLS_CC);
00784 
00785               result->unbuf->last_row_data = row_packet->fields;
00786               result->unbuf->last_row_buffer = row_packet->row_buffer;
00787               row_packet->fields = NULL;
00788               row_packet->row_buffer = NULL;
00789 
00790               MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF);
00791 
00792               if (!row_packet->skip_extraction) {
00793                      HashTable *row_ht = Z_ARRVAL_P(row);
00794                      MYSQLND_FIELD *field = result->meta->fields;
00795                      struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
00796                      unsigned int i, field_count = result->field_count;
00797                      unsigned long *lengths = result->lengths;
00798 
00799                      enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer,
00800                                                                              result->unbuf->last_row_data,
00801                                                                              field_count,
00802                                                                              row_packet->fields_metadata,
00803                                                                              FALSE,
00804                                                                              result->conn->options.numeric_and_datetime_as_unicode,
00805                                                                              result->conn->options.int_and_float_native,
00806                                                                              result->conn->stats TSRMLS_CC);
00807                      if (PASS != rc) {
00808                             DBG_RETURN(FAIL);
00809                      }
00810                      for (i = 0; i < field_count; i++, field++, hash_key++) {
00811                             zval *data = result->unbuf->last_row_data[i];
00812                             unsigned int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data);
00813 
00814                             if (lengths) {
00815                                    lengths[i] = len;
00816                             }
00817 
00818                             if (flags & MYSQLND_FETCH_NUM) {
00819                                    Z_ADDREF_P(data);
00820                                    zend_hash_next_index_insert(row_ht, &data, sizeof(zval *), NULL);
00821                             }
00822                             if (flags & MYSQLND_FETCH_ASSOC) {
00823                                    /* zend_hash_quick_update needs length + trailing zero */
00824                                    /* QQ: Error handling ? */
00825                                    /*
00826                                      zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
00827                                      the index is a numeric and convert it to it. This however means constant
00828                                      hashing of the column name, which is not needed as it can be precomputed.
00829                                    */
00830                                    Z_ADDREF_P(data);
00831                                    if (hash_key->is_numeric == FALSE) {
00832 #if MYSQLND_UNICODE
00833                                           zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE,
00834                                                                                      hash_key->ustr,
00835                                                                                      hash_key->ulen + 1,
00836                                                                                      hash_key->key,
00837                                                                                      (void *) &data, sizeof(zval *), NULL);
00838 #else
00839                                           zend_hash_quick_update(Z_ARRVAL_P(row),
00840                                                                                 field->name,
00841                                                                                 field->name_length + 1,
00842                                                                                 hash_key->key,
00843                                                                                 (void *) &data, sizeof(zval *), NULL);
00844 #endif
00845                                    } else {
00846                                           zend_hash_index_update(Z_ARRVAL_P(row),
00847                                                                                 hash_key->key,
00848                                                                                 (void *) &data, sizeof(zval *), NULL);
00849                                    }
00850                             }
00851                             if (field->max_length < len) {
00852                                    field->max_length = len;
00853                             }
00854                      }
00855               }
00856               *fetched_anything = TRUE;
00857               result->unbuf->row_count++;
00858        } else if (ret == FAIL) {
00859               if (row_packet->error_info.error_no) {
00860                      result->conn->error_info = row_packet->error_info;
00861                      DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error);
00862               }
00863               CONN_SET_STATE(result->conn, CONN_READY);
00864               result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
00865        } else if (row_packet->eof) {
00866               /* Mark the connection as usable again */
00867               DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status);
00868               result->unbuf->eof_reached = TRUE;
00869               result->conn->upsert_status.warning_count = row_packet->warning_count;
00870               result->conn->upsert_status.server_status = row_packet->server_status;
00871               /*
00872                 result->row_packet will be cleaned when
00873                 destroying the result object
00874               */
00875               if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
00876                      CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
00877               } else {
00878                      CONN_SET_STATE(result->conn, CONN_READY);
00879               }
00880               result->m.unbuffered_free_last_data(result TSRMLS_CC);
00881        }
00882 
00883        DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
00884        DBG_RETURN(PASS);
00885 }
00886 /* }}} */
00887 
00888 
00889 /* {{{ mysqlnd_res::use_result */
00890 static MYSQLND_RES *
00891 MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, zend_bool ps TSRMLS_DC)
00892 {
00893        DBG_ENTER("mysqlnd_res::use_result");
00894        DBG_INF_FMT("ps=%u", ps);
00895 
00896        SET_EMPTY_ERROR(result->conn->error_info);
00897 
00898 
00899        if (ps == FALSE) {
00900               result->type                = MYSQLND_RES_NORMAL;
00901               result->m.fetch_row         = result->m.fetch_row_normal_unbuffered;
00902               result->m.fetch_lengths     = mysqlnd_fetch_lengths_unbuffered;
00903               result->m.row_decoder       = php_mysqlnd_rowp_read_text_protocol;
00904               result->lengths                    = mnd_ecalloc(result->field_count, sizeof(unsigned long));
00905               if (!result->lengths) {
00906                      goto oom;
00907               }
00908        } else {
00909               result->type                = MYSQLND_RES_PS_UNBUF;
00910               result->m.fetch_row         = NULL;
00911               /* result->m.fetch_row() will be set in mysqlnd_ps.c */
00912               result->m.fetch_lengths     = NULL; /* makes no sense */
00913               result->m.row_decoder       = php_mysqlnd_rowp_read_binary_protocol;
00914               result->lengths             = NULL;
00915        }
00916 
00917        result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC);
00918        result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED));
00919        if (!result->result_set_memory_pool || !result->unbuf) {
00920               goto oom;
00921        }
00922 
00923        /*
00924          Will be freed in the mysqlnd_internal_free_result_contents() called
00925          by the resource destructor. mysqlnd_fetch_row_unbuffered() expects
00926          this to be not NULL.
00927        */
00928        /* FALSE = non-persistent */
00929        result->row_packet = result->conn->protocol->m.get_row_packet(result->conn->protocol, FALSE TSRMLS_CC);
00930        if (!result->row_packet) {
00931               goto oom;
00932        }
00933        result->row_packet->result_set_memory_pool = result->result_set_memory_pool;
00934        result->row_packet->field_count = result->field_count;
00935        result->row_packet->binary_protocol = ps;
00936        result->row_packet->fields_metadata = result->meta->fields;
00937        result->row_packet->bit_fields_count = result->meta->bit_fields_count;
00938        result->row_packet->bit_fields_total_len = result->meta->bit_fields_total_len;
00939 
00940        DBG_RETURN(result);
00941 oom:
00942        SET_OOM_ERROR(result->conn->error_info);
00943        DBG_RETURN(NULL);
00944 }
00945 /* }}} */
00946 
00947 
00948 /* {{{ mysqlnd_fetch_row_buffered_c */
00949 static MYSQLND_ROW_C
00950 mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC)
00951 {
00952        MYSQLND_ROW_C ret = NULL;
00953        MYSQLND_RES_BUFFERED *set = result->stored_data;
00954 
00955        DBG_ENTER("mysqlnd_fetch_row_buffered_c");
00956 
00957        /* If we haven't read everything */
00958        if (set->data_cursor &&
00959               (set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
00960        {
00961               zval **current_row = set->data_cursor;
00962               MYSQLND_FIELD *field = result->meta->fields;
00963               struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
00964               unsigned int i;
00965 
00966               if (NULL == current_row[0]) {
00967                      uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count;
00968                      enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num],
00969                                                                              current_row,
00970                                                                              result->meta->field_count,
00971                                                                              result->meta->fields,
00972                                                                              FALSE,
00973                                                                              result->conn->options.numeric_and_datetime_as_unicode,
00974                                                                              result->conn->options.int_and_float_native,
00975                                                                              result->conn->stats TSRMLS_CC);
00976                      if (rc != PASS) {
00977                             DBG_RETURN(ret);
00978                      }
00979                      set->initialized_rows++;
00980                      for (i = 0; i < result->field_count; i++) {
00981                             /*
00982                               NULL fields are 0 length, 0 is not more than 0
00983                               String of zero size, definitely can't be the next max_length.
00984                               Thus for NULL and zero-length we are quite efficient.
00985                             */
00986                             if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
00987                                    unsigned long len = Z_STRLEN_P(current_row[i]);
00988                                    if (field->max_length < len) {
00989                                           field->max_length = len;
00990                                    }
00991                             }
00992                      }
00993               }
00994 
00995               set->data_cursor += result->meta->field_count;
00996               MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
00997 
00998               ret = mnd_malloc(result->field_count * sizeof(char *));
00999               if (ret) {
01000                      for (i = 0; i < result->field_count; i++, field++, hash_key++) {
01001                             zval *data = current_row[i];
01002 
01003                             if (Z_TYPE_P(data) != IS_NULL) {
01004                                    convert_to_string(data);
01005                                    ret[i] = Z_STRVAL_P(data);
01006                             } else {
01007                                    ret[i] = NULL;
01008                             }
01009                      }
01010               }
01011               /* there is no conn handle in this function thus we can't set OOM in error_info */
01012        } else {
01013               set->data_cursor = NULL;
01014               DBG_INF("EOF reached");
01015        }
01016        DBG_RETURN(ret);
01017 }
01018 /* }}} */
01019 
01020 
01021 /* {{{ mysqlnd_fetch_row_buffered */
01022 static enum_func_status
01023 mysqlnd_fetch_row_buffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
01024 {
01025        unsigned int i;
01026        zval *row = (zval *) param;
01027        MYSQLND_RES_BUFFERED *set = result->stored_data;
01028        enum_func_status ret = FAIL;
01029 
01030        DBG_ENTER("mysqlnd_fetch_row_buffered");
01031        DBG_INF_FMT("flags=%u row=%p", flags, row);
01032 
01033        /* If we haven't read everything */
01034        if (set->data_cursor &&
01035               (set->data_cursor - set->data) < (set->row_count * result->meta->field_count))
01036        {
01037               zval **current_row = set->data_cursor;
01038               MYSQLND_FIELD *field = result->meta->fields;
01039               struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys;
01040 
01041               if (NULL == current_row[0]) {
01042                      uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count;
01043                      enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num],
01044                                                                              current_row,
01045                                                                              result->meta->field_count,
01046                                                                              result->meta->fields,
01047                                                                              result->stored_data->persistent,
01048                                                                              result->conn->options.numeric_and_datetime_as_unicode,
01049                                                                              result->conn->options.int_and_float_native,
01050                                                                              result->conn->stats TSRMLS_CC);
01051                      if (rc != PASS) {
01052                             DBG_RETURN(FAIL);
01053                      }
01054                      set->initialized_rows++;
01055                      for (i = 0; i < result->field_count; i++) {
01056                             /*
01057                               NULL fields are 0 length, 0 is not more than 0
01058                               String of zero size, definitely can't be the next max_length.
01059                               Thus for NULL and zero-length we are quite efficient.
01060                             */
01061                             if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
01062                                    unsigned long len = Z_STRLEN_P(current_row[i]);
01063                                    if (field->max_length < len) {
01064                                           field->max_length = len;
01065                                    }
01066                             }
01067                      }
01068               }
01069 
01070               for (i = 0; i < result->field_count; i++, field++, hash_key++) {
01071                      zval *data = current_row[i];
01072 
01073                      if (flags & MYSQLND_FETCH_NUM) {
01074                             Z_ADDREF_P(data);
01075                             zend_hash_next_index_insert(Z_ARRVAL_P(row), &data, sizeof(zval *), NULL);
01076                      }
01077                      if (flags & MYSQLND_FETCH_ASSOC) {
01078                             /* zend_hash_quick_update needs length + trailing zero */
01079                             /* QQ: Error handling ? */
01080                             /*
01081                               zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether
01082                               the index is a numeric and convert it to it. This however means constant
01083                               hashing of the column name, which is not needed as it can be precomputed.
01084                             */
01085                             Z_ADDREF_P(data);
01086                             if (hash_key->is_numeric == FALSE) {
01087 #if MYSQLND_UNICODE
01088                                    zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE,
01089                                                                               hash_key->ustr,
01090                                                                               hash_key->ulen + 1,
01091                                                                               hash_key->key,
01092                                                                               (void *) &data, sizeof(zval *), NULL);
01093 #else
01094                                    zend_hash_quick_update(Z_ARRVAL_P(row),
01095                                                                          field->name,
01096                                                                          field->name_length + 1,
01097                                                                          hash_key->key,
01098                                                                          (void *) &data, sizeof(zval *), NULL);
01099 #endif
01100                             } else {
01101                                    zend_hash_index_update(Z_ARRVAL_P(row),
01102                                                                          hash_key->key,
01103                                                                          (void *) &data, sizeof(zval *), NULL);
01104                             }
01105                      }
01106               }
01107               set->data_cursor += result->meta->field_count;
01108               *fetched_anything = TRUE;
01109               MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF);
01110               ret = PASS;
01111        } else {
01112               set->data_cursor = NULL;
01113               *fetched_anything = FALSE;
01114               ret = PASS;
01115               DBG_INF("EOF reached");
01116        }
01117        DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything);
01118        DBG_RETURN(ret);
01119 }
01120 /* }}} */
01121 
01122 
01123 #define STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY 2
01124 
01125 /* {{{ mysqlnd_res::store_result_fetch_data */
01126 enum_func_status
01127 MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND * const conn, MYSQLND_RES * result,
01128                                                                                            MYSQLND_RES_METADATA *meta,
01129                                                                                            zend_bool binary_protocol,
01130                                                                                            zend_bool to_cache TSRMLS_DC)
01131 {
01132        enum_func_status ret;
01133        MYSQLND_PACKET_ROW *row_packet = NULL;
01134        unsigned int next_extend = STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY, free_rows = 1;
01135        MYSQLND_RES_BUFFERED *set;
01136 
01137        DBG_ENTER("mysqlnd_res::store_result_fetch_data");
01138        DBG_INF_FMT("conn=%llu binary_proto=%u to_cache=%u",
01139                             conn->thread_id, binary_protocol, to_cache);
01140 
01141        result->stored_data  = set = mnd_pecalloc(1, sizeof(MYSQLND_RES_BUFFERED), to_cache);
01142        if (!set) {
01143               SET_OOM_ERROR(conn->error_info);
01144               ret = FAIL;
01145               goto end;
01146        }
01147        if (free_rows) {
01148               set->row_buffers = mnd_pemalloc((size_t)(free_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)), to_cache);
01149               if (!set->row_buffers) {
01150                      SET_OOM_ERROR(conn->error_info);
01151                      ret = FAIL;
01152                      goto end;
01153               }
01154        }
01155        set->persistent      = to_cache;
01156        set->references      = 1;
01157 
01158        result->m.row_decoder = binary_protocol? php_mysqlnd_rowp_read_binary_protocol:
01159                                                                               php_mysqlnd_rowp_read_text_protocol;
01160 
01161        /* non-persistent */
01162        row_packet = conn->protocol->m.get_row_packet(conn->protocol, FALSE TSRMLS_CC);
01163        if (!row_packet) {
01164               SET_OOM_ERROR(conn->error_info);
01165               ret = FAIL;
01166               goto end;
01167        }
01168        row_packet->result_set_memory_pool = result->result_set_memory_pool;
01169        row_packet->field_count = meta->field_count;
01170        row_packet->binary_protocol = binary_protocol;
01171        row_packet->fields_metadata = meta->fields;
01172        row_packet->bit_fields_count       = meta->bit_fields_count;
01173        row_packet->bit_fields_total_len = meta->bit_fields_total_len;
01174 
01175        row_packet->skip_extraction = TRUE; /* let php_mysqlnd_rowp_read() not allocate row_packet->fields, we will do it */
01176 
01177        while (FAIL != (ret = PACKET_READ(row_packet, conn)) && !row_packet->eof) {
01178               if (!free_rows) {
01179                      uint64_t total_allocated_rows = free_rows = next_extend = next_extend * 11 / 10; /* extend with 10% */
01180                      MYSQLND_MEMORY_POOL_CHUNK ** new_row_buffers;
01181                      total_allocated_rows += set->row_count;
01182 
01183                      /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
01184                      if (total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) {
01185                             SET_OOM_ERROR(conn->error_info);
01186                             ret = FAIL;
01187                             goto end;
01188                      }
01189                      new_row_buffers = mnd_perealloc(set->row_buffers,
01190                                                                              (size_t)(total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)),
01191                                                                              set->persistent);
01192                      if (!new_row_buffers) {
01193                             SET_OOM_ERROR(conn->error_info);
01194                             ret = FAIL;
01195                             goto end;
01196                      }
01197                      set->row_buffers = new_row_buffers;
01198               }
01199               free_rows--;
01200               set->row_buffers[set->row_count] = row_packet->row_buffer;
01201 
01202               set->row_count++;
01203 
01204               /* So row_packet's destructor function won't efree() it */
01205               row_packet->fields = NULL;
01206               row_packet->row_buffer = NULL;
01207 
01208               /*
01209                 No need to FREE_ALLOCA as we can reuse the
01210                 'lengths' and 'fields' arrays. For lengths its absolutely safe.
01211                 'fields' is reused because the ownership of the strings has been
01212                 transfered above. 
01213               */
01214        }
01215        /* Overflow ? */
01216        if (set->row_count) {
01217               /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
01218               if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) {
01219                      SET_OOM_ERROR(conn->error_info);
01220                      ret = FAIL;
01221                      goto end;
01222               }
01223               /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */
01224               set->data = mnd_pemalloc((size_t)(set->row_count * meta->field_count * sizeof(zval *)), to_cache);
01225               if (!set->data) {
01226                      SET_OOM_ERROR(conn->error_info);
01227                      ret = FAIL;
01228                      goto end;
01229               }
01230               memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval *)));
01231        }
01232 
01233        MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats,
01234                                                                   binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS:
01235                                                                                                   STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL,
01236                                                                   set->row_count);
01237 
01238        /* Finally clean */
01239        if (row_packet->eof) { 
01240               conn->upsert_status.warning_count = row_packet->warning_count;
01241               conn->upsert_status.server_status = row_packet->server_status;
01242        }
01243        /* save some memory */
01244        if (free_rows) {
01245               /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */
01246               if (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) {
01247                      SET_OOM_ERROR(conn->error_info);
01248                      ret = FAIL;
01249                      goto end;
01250               }
01251               set->row_buffers = mnd_perealloc(set->row_buffers,
01252                                                                        (size_t) (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)),
01253                                                                        set->persistent);
01254        }
01255 
01256        if (conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
01257               CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING);
01258        } else {
01259               CONN_SET_STATE(conn, CONN_READY);
01260        }
01261 
01262        if (ret == FAIL) {
01263               set->error_info = row_packet->error_info;
01264        } else {
01265               /* Position at the first row */
01266               set->data_cursor = set->data;
01267 
01268               /* libmysql's documentation says it should be so for SELECT statements */
01269               conn->upsert_status.affected_rows = set->row_count;
01270        }
01271        DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u",
01272                             ret == PASS? "PASS":"FAIL", (uint) set->row_count, conn->upsert_status.warning_count, conn->upsert_status.server_status);
01273 end:
01274        PACKET_FREE(row_packet);
01275 
01276        DBG_RETURN(ret);
01277 }
01278 /* }}} */
01279 
01280 
01281 /* {{{ mysqlnd_res::store_result */
01282 static MYSQLND_RES *
01283 MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result,
01284                                                                         MYSQLND * const conn,
01285                                                                         zend_bool ps_protocol TSRMLS_DC)
01286 {
01287        enum_func_status ret;
01288        zend_bool to_cache = FALSE;
01289 
01290        DBG_ENTER("mysqlnd_res::store_result");
01291        DBG_INF_FMT("conn=%u ps_protocol=%u", conn->thread_id, ps_protocol);
01292 
01293        /* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */
01294        result->conn                = conn->m->get_reference(conn TSRMLS_CC);
01295        result->type                = MYSQLND_RES_NORMAL;
01296        result->m.fetch_row         = result->m.fetch_row_normal_buffered;
01297        result->m.fetch_lengths     = mysqlnd_fetch_lengths_buffered;
01298 
01299        result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC);
01300        result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long));
01301        if (!result->result_set_memory_pool || !result->lengths) {
01302               SET_OOM_ERROR(conn->error_info);
01303               DBG_RETURN(NULL);
01304        }
01305 
01306        CONN_SET_STATE(conn, CONN_FETCHING_DATA);
01307 
01308        ret = result->m.store_result_fetch_data(conn, result, result->meta, ps_protocol, to_cache TSRMLS_CC);
01309        if (FAIL == ret) {
01310               if (result->stored_data) {
01311                      conn->error_info = result->stored_data->error_info;
01312               } else {
01313                      SET_OOM_ERROR(conn->error_info);
01314               }
01315               DBG_RETURN(NULL);
01316        }
01317        /* libmysql's documentation says it should be so for SELECT statements */
01318        conn->upsert_status.affected_rows = result->stored_data->row_count;
01319 
01320        DBG_RETURN(result);
01321 }
01322 /* }}} */
01323 
01324 
01325 /* {{{ mysqlnd_res::skip_result */
01326 static enum_func_status
01327 MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result TSRMLS_DC)
01328 {
01329        zend_bool fetched_anything;
01330 
01331        DBG_ENTER("mysqlnd_res::skip_result");
01332        /*
01333          Unbuffered sets
01334          A PS could be prepared - there is metadata and thus a stmt->result but the
01335          fetch_row function isn't actually set (NULL), thus we have to skip these.
01336        */
01337        if (!result->stored_data && result->unbuf &&
01338               !result->unbuf->eof_reached && result->m.fetch_row)
01339        {
01340               DBG_INF("skipping result");
01341               /* We have to fetch all data to clean the line */
01342               MYSQLND_INC_CONN_STATISTIC(result->conn->stats,
01343                                                                result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS:
01344                                                                                                                               STAT_FLUSHED_PS_SETS);
01345 
01346               while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything TSRMLS_CC)) && fetched_anything == TRUE) {
01347                      /* do nothing */;
01348               }
01349        }
01350        DBG_RETURN(PASS);
01351 }
01352 /* }}} */
01353 
01354 
01355 /* {{{ mysqlnd_res::free_result */
01356 static enum_func_status
01357 MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC)
01358 {
01359        DBG_ENTER("mysqlnd_res::free_result");
01360        DBG_INF_FMT("implicit=%u", implicit);
01361 
01362        result->m.skip_result(result TSRMLS_CC);
01363        MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL,
01364                                                     implicit == TRUE? STAT_FREE_RESULT_IMPLICIT:
01365                                                                                            STAT_FREE_RESULT_EXPLICIT);
01366 
01367        result->m.free_result_internal(result TSRMLS_CC);
01368        DBG_RETURN(PASS);
01369 }
01370 /* }}} */
01371 
01372 
01373 /* {{{ mysqlnd_res::data_seek */
01374 static enum_func_status
01375 MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * result, uint64_t row TSRMLS_DC)
01376 {
01377        DBG_ENTER("mysqlnd_res::data_seek");
01378        DBG_INF_FMT("row=%lu", row);
01379 
01380        if (!result->stored_data) {
01381               return FAIL;
01382        }
01383 
01384        /* libmysql just moves to the end, it does traversing of a linked list */
01385        if (row >= result->stored_data->row_count) {
01386               result->stored_data->data_cursor = NULL;
01387        } else {
01388               result->stored_data->data_cursor = result->stored_data->data + row * result->meta->field_count;
01389        }
01390 
01391        DBG_RETURN(PASS);
01392 }
01393 /* }}} */
01394 
01395 
01396 /* {{{ mysqlnd_res::num_rows */
01397 static uint64_t
01398 MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result TSRMLS_DC)
01399 {
01400        /* Be compatible with libmysql. We count row_count, but will return 0 */
01401        return result->stored_data? result->stored_data->row_count:(result->unbuf && result->unbuf->eof_reached? result->unbuf->row_count:0);
01402 }
01403 /* }}} */
01404 
01405 
01406 /* {{{ mysqlnd_res::num_fields */
01407 static unsigned int
01408 MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result TSRMLS_DC)
01409 {
01410        return result->field_count;
01411 }
01412 /* }}} */
01413 
01414 
01415 /* {{{ mysqlnd_res::fetch_field */
01416 static const MYSQLND_FIELD *
01417 MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result TSRMLS_DC)
01418 {
01419        DBG_ENTER("mysqlnd_res::fetch_field");
01420        do {
01421               if (result->meta) {
01422                      /*
01423                        We optimize the result set, so we don't convert all the data from raw buffer format to
01424                        zval arrays during store. In the case someone doesn't read all the lines this will
01425                        save time. However, when a metadata call is done, we need to calculate max_length.
01426                        We don't have control whether max_length will be used, unfortunately. Otherwise we
01427                        could have been able to skip that step.
01428                        Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
01429                        then we can have max_length as dynamic property, which will be calculated during runtime and
01430                        not during mysqli_fetch_field() time.
01431                      */
01432                      if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
01433                             DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
01434                             /* we have to initialize the rest to get the updated max length */
01435                             if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
01436                                    break;
01437                             }
01438                      }
01439                      DBG_RETURN(result->meta->m->fetch_field(result->meta TSRMLS_CC));
01440               }
01441        } while (0);
01442        DBG_RETURN(NULL);
01443 }
01444 /* }}} */
01445 
01446 
01447 /* {{{ mysqlnd_res::fetch_field_direct */
01448 static const MYSQLND_FIELD *
01449 MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC)
01450 {
01451        DBG_ENTER("mysqlnd_res::fetch_field_direct");
01452        do {
01453               if (result->meta) {
01454                      /*
01455                        We optimize the result set, so we don't convert all the data from raw buffer format to
01456                        zval arrays during store. In the case someone doesn't read all the lines this will
01457                        save time. However, when a metadata call is done, we need to calculate max_length.
01458                        We don't have control whether max_length will be used, unfortunately. Otherwise we
01459                        could have been able to skip that step.
01460                        Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata,
01461                        then we can have max_length as dynamic property, which will be calculated during runtime and
01462                        not during mysqli_fetch_field_direct() time.
01463                      */
01464                      if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
01465                             DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request");
01466                             /* we have to initialized the rest to get the updated max length */
01467                             if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
01468                                    break;
01469                             }
01470                      }
01471                      DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr TSRMLS_CC));
01472               }
01473        } while (0);
01474 
01475        DBG_RETURN(NULL);
01476 }
01477 /* }}} */
01478 
01479 
01480 /* {{{ mysqlnd_res::fetch_field */
01481 static const MYSQLND_FIELD *
01482 MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result TSRMLS_DC)
01483 {
01484        DBG_ENTER("mysqlnd_res::fetch_fields");
01485        do {
01486               if (result->meta) {
01487                      if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) {
01488                             /* we have to initialize the rest to get the updated max length */
01489                             if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) {
01490                                    break;
01491                             }
01492                      }
01493                      DBG_RETURN(result->meta->m->fetch_fields(result->meta TSRMLS_CC));
01494               }
01495        } while (0);
01496        DBG_RETURN(NULL);
01497 }
01498 /* }}} */
01499 
01500 
01501 
01502 /* {{{ mysqlnd_res::field_seek */
01503 static MYSQLND_FIELD_OFFSET
01504 MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC)
01505 {
01506        MYSQLND_FIELD_OFFSET return_value = 0;
01507        if (result->meta) {
01508               return_value = result->meta->current_field;
01509               result->meta->current_field = field_offset;
01510        }
01511        return return_value;
01512 }
01513 /* }}} */
01514 
01515 
01516 /* {{{ mysqlnd_res::field_tell */
01517 static MYSQLND_FIELD_OFFSET
01518 MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result TSRMLS_DC)
01519 {
01520        return result->meta? result->meta->m->field_tell(result->meta TSRMLS_CC) : 0;
01521 }
01522 /* }}} */
01523 
01524 
01525 /* for php_addslashes */
01526 #include "ext/standard/php_string.h"
01527 
01528 /* {{{ mysqlnd_res::fetch_into */
01529 static void
01530 MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, unsigned int flags,
01531                                                                       zval *return_value,
01532                                                                       enum_mysqlnd_extension extension TSRMLS_DC ZEND_FILE_LINE_DC)
01533 {
01534        zend_bool fetched_anything;
01535 
01536        DBG_ENTER("mysqlnd_res::fetch_into");
01537        DBG_INF_FMT("flags=%u mysqlnd_extension=%u", flags, extension);
01538 
01539        if (!result->m.fetch_row) {
01540               RETVAL_NULL();
01541               DBG_VOID_RETURN;
01542        }
01543        /*
01544          Hint Zend how many elements we will have in the hash. Thus it won't
01545          extend and rehash the hash constantly.
01546        */
01547        mysqlnd_array_init(return_value, mysqlnd_num_fields(result) * 2);
01548        if (FAIL == result->m.fetch_row(result, (void *)return_value, flags, &fetched_anything TSRMLS_CC)) {
01549               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading a row");
01550               RETVAL_FALSE;
01551        } else if (fetched_anything == FALSE) {
01552               zval_dtor(return_value);
01553               switch (extension) {
01554                      case MYSQLND_MYSQLI:
01555                             RETVAL_NULL();
01556                             break;
01557                      case MYSQLND_MYSQL:
01558                             RETVAL_FALSE;
01559                             break;
01560                      default:exit(0);
01561               }
01562        } 
01563        /*
01564          return_value is IS_NULL for no more data and an array for data. Thus it's ok
01565          to return here.
01566        */
01567        DBG_VOID_RETURN;
01568 }
01569 /* }}} */
01570 
01571 
01572 /* {{{ mysqlnd_res::fetch_row_c */
01573 static MYSQLND_ROW_C
01574 MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result TSRMLS_DC)
01575 {
01576        MYSQLND_ROW_C ret = NULL;
01577        DBG_ENTER("mysqlnd_res::fetch_row_c");
01578 
01579        if (result->m.fetch_row) {
01580               if (result->m.fetch_row == result->m.fetch_row_normal_buffered) {
01581                      DBG_RETURN(mysqlnd_fetch_row_buffered_c(result TSRMLS_CC));
01582               } else if (result->m.fetch_row == result->m.fetch_row_normal_unbuffered) {
01583                      DBG_RETURN(mysqlnd_fetch_row_unbuffered_c(result TSRMLS_CC));
01584               } else {
01585                      php_error_docref(NULL TSRMLS_CC, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers");
01586               }
01587        }
01588        DBG_RETURN(ret);
01589 }
01590 /* }}} */
01591 
01592 
01593 /* {{{ mysqlnd_res::fetch_all */
01594 static void
01595 MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC)
01596 {
01597        zval  *row;
01598        ulong i = 0;
01599        MYSQLND_RES_BUFFERED *set = result->stored_data;
01600 
01601        DBG_ENTER("mysqlnd_res::fetch_all");
01602        DBG_INF_FMT("flags=%u", flags);
01603 
01604        if ((!result->unbuf && !set)) {
01605               php_error_docref(NULL TSRMLS_CC, E_WARNING, "fetch_all can be used only with buffered sets");
01606               if (result->conn) {
01607                      SET_CLIENT_ERROR(result->conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "fetch_all can be used only with buffered sets");
01608               }
01609               RETVAL_NULL();
01610               DBG_VOID_RETURN;
01611        }
01612 
01613        /* 4 is a magic value. The cast is safe, if larger then the array will be later extended - no big deal :) */
01614        mysqlnd_array_init(return_value, set? (unsigned int) set->row_count : 4); 
01615 
01616        do {
01617               MAKE_STD_ZVAL(row);
01618               mysqlnd_fetch_into(result, flags, row, MYSQLND_MYSQLI);
01619               if (Z_TYPE_P(row) != IS_ARRAY) {
01620                      zval_ptr_dtor(&row);
01621                      break;
01622               }
01623               add_index_zval(return_value, i++, row);
01624        } while (1);
01625 
01626        DBG_VOID_RETURN;
01627 }
01628 /* }}} */
01629 
01630 
01631 /* {{{ mysqlnd_res::fetch_field_data */
01632 static void
01633 MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, unsigned int offset, zval *return_value TSRMLS_DC)
01634 {
01635        zval row;
01636        zval **entry;
01637        unsigned int i = 0;
01638 
01639        DBG_ENTER("mysqlnd_res::fetch_field_data");
01640        DBG_INF_FMT("offset=%u", offset);
01641 
01642        if (!result->m.fetch_row) {
01643               RETVAL_NULL();
01644               DBG_VOID_RETURN;
01645        }
01646        /*
01647          Hint Zend how many elements we will have in the hash. Thus it won't
01648          extend and rehash the hash constantly.
01649        */
01650        INIT_PZVAL(&row);
01651        mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, &row, MYSQLND_MYSQL);
01652        if (Z_TYPE(row) != IS_ARRAY) {
01653               zval_dtor(&row);
01654               RETVAL_NULL();
01655               DBG_VOID_RETURN;
01656        }
01657        zend_hash_internal_pointer_reset(Z_ARRVAL(row));
01658        while (i++ < offset) {
01659               zend_hash_move_forward(Z_ARRVAL(row));
01660               zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry);
01661        }
01662 
01663        zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry);
01664 
01665        *return_value = **entry;
01666        zval_copy_ctor(return_value);
01667        Z_SET_REFCOUNT_P(return_value, 1);
01668        zval_dtor(&row);
01669 
01670        DBG_VOID_RETURN;
01671 }
01672 /* }}} */
01673 
01674 
01675 static 
01676 MYSQLND_CLASS_METHODS_START(mysqlnd_res)
01677        NULL, /* fetch_row */
01678        mysqlnd_fetch_row_buffered,
01679        mysqlnd_fetch_row_unbuffered,
01680        MYSQLND_METHOD(mysqlnd_res, use_result),
01681        MYSQLND_METHOD(mysqlnd_res, store_result),
01682        MYSQLND_METHOD(mysqlnd_res, fetch_into),
01683        MYSQLND_METHOD(mysqlnd_res, fetch_row_c),
01684        MYSQLND_METHOD(mysqlnd_res, fetch_all),
01685        MYSQLND_METHOD(mysqlnd_res, fetch_field_data),
01686        MYSQLND_METHOD(mysqlnd_res, num_rows),
01687        MYSQLND_METHOD(mysqlnd_res, num_fields),
01688        MYSQLND_METHOD(mysqlnd_res, skip_result),
01689        MYSQLND_METHOD(mysqlnd_res, data_seek),
01690        MYSQLND_METHOD(mysqlnd_res, field_seek),
01691        MYSQLND_METHOD(mysqlnd_res, field_tell),
01692        MYSQLND_METHOD(mysqlnd_res, fetch_field),
01693        MYSQLND_METHOD(mysqlnd_res, fetch_field_direct),
01694        MYSQLND_METHOD(mysqlnd_res, fetch_fields),
01695        MYSQLND_METHOD(mysqlnd_res, read_result_metadata),
01696        NULL, /* fetch_lengths */
01697        MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data),
01698        MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest),
01699        MYSQLND_METHOD(mysqlnd_res, free_result_buffers),
01700        MYSQLND_METHOD(mysqlnd_res, free_result),
01701 
01702        mysqlnd_internal_free_result, /* free_result_internal */
01703        mysqlnd_internal_free_result_contents, /* free_result_contents */
01704        MYSQLND_METHOD(mysqlnd_res, free_buffered_data),
01705        MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data),
01706 
01707        NULL /* row_decoder */,
01708        mysqlnd_result_meta_init
01709 MYSQLND_CLASS_METHODS_END;
01710 
01711 
01712 /* {{{ mysqlnd_result_init */
01713 PHPAPI MYSQLND_RES *
01714 mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC)
01715 {
01716        size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *);
01717        MYSQLND_RES *ret = mnd_pecalloc(1, alloc_size, persistent);
01718 
01719        DBG_ENTER("mysqlnd_result_init");
01720        DBG_INF_FMT("field_count=%u", field_count);
01721 
01722        if (!ret) {
01723               DBG_RETURN(NULL);
01724        }
01725 
01726        ret->persistent             = persistent;
01727        ret->field_count     = field_count;
01728        ret->m = mysqlnd_mysqlnd_res_methods;
01729 
01730        DBG_RETURN(ret);
01731 }
01732 /* }}} */
01733 
01734 
01735 /* {{{ _mysqlnd_plugin_get_plugin_result_data */
01736 PHPAPI void ** _mysqlnd_plugin_get_plugin_result_data(const MYSQLND_RES * result, unsigned int plugin_id TSRMLS_DC)
01737 {
01738        DBG_ENTER("_mysqlnd_plugin_get_plugin_result_data");
01739        DBG_INF_FMT("plugin_id=%u", plugin_id);
01740        if (!result || plugin_id >= mysqlnd_plugin_count()) {
01741               return NULL;
01742        }
01743        DBG_RETURN((void *)((char *)result + sizeof(MYSQLND_RES) + plugin_id * sizeof(void *)));
01744 }
01745 /* }}} */
01746 
01747 
01748 /* {{{ mysqlnd_result_get_methods */
01749 PHPAPI struct st_mysqlnd_res_methods *
01750 mysqlnd_result_get_methods()
01751 {
01752        return &mysqlnd_mysqlnd_res_methods;
01753 }
01754 /* }}} */
01755 
01756 /*
01757  * Local variables:
01758  * tab-width: 4
01759  * c-basic-offset: 4
01760  * End:
01761  * vim600: noet sw=4 ts=4 fdm=marker
01762  * vim<600: noet sw=4 ts=4
01763  */