Back to index

php5  5.3.10
mysqlnd_ps.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_ps.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_priv.h"
00026 #include "mysqlnd_result.h"
00027 #include "mysqlnd_result_meta.h"
00028 #include "mysqlnd_statistics.h"
00029 #include "mysqlnd_debug.h"
00030 #include "mysqlnd_block_alloc.h"
00031 
00032 
00033 #define MYSQLND_SILENT
00034 
00035 
00036 const char * const mysqlnd_not_bound_as_blob = "Can't send long data for non-string/non-binary data types";
00037 const char * const mysqlnd_stmt_not_prepared = "Statement not prepared";
00038 
00039 static struct st_mysqlnd_stmt_methods *mysqlnd_stmt_methods;
00040 
00041 /* Exported by mysqlnd_ps_codec.c */
00042 enum_func_status mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC);
00043 
00044 enum_func_status mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param,
00045                                                                                     unsigned int flags,
00046                                                                                     zend_bool *fetched_anything TSRMLS_DC);
00047 
00048 enum_func_status mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param,
00049                                                                                 unsigned int flags,
00050                                                                                 zend_bool *fetched_anything TSRMLS_DC);
00051 
00052 static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC);
00053 static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC);
00054 
00055 /* {{{ mysqlnd_stmt::store_result */
00056 static MYSQLND_RES *
00057 MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s TSRMLS_DC)
00058 {
00059        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00060        enum_func_status ret;
00061        MYSQLND * conn;
00062        MYSQLND_RES * result;
00063        zend_bool to_cache = FALSE;
00064 
00065        DBG_ENTER("mysqlnd_stmt::store_result");
00066        if (!stmt || !stmt->conn || !stmt->result) {
00067               DBG_RETURN(NULL);
00068        }
00069        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
00070 
00071        conn = stmt->conn;
00072 
00073        /* be compliant with libmysql - NULL will turn */
00074        if (!stmt->field_count) {
00075               DBG_RETURN(NULL);
00076        }
00077 
00078        if (stmt->cursor_exists) {
00079               /* Silently convert buffered to unbuffered, for now */
00080               DBG_RETURN(s->m->use_result(s TSRMLS_CC));
00081        }
00082 
00083        /* Nothing to store for UPSERT/LOAD DATA*/
00084        if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA ||
00085               stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE)
00086        {
00087               SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
00088                                            UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
00089               DBG_RETURN(NULL);
00090        }
00091 
00092        stmt->default_rset_handler = s->m->store_result;
00093 
00094        SET_EMPTY_ERROR(stmt->error_info);
00095        SET_EMPTY_ERROR(stmt->conn->error_info);
00096        MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_PS_BUFFERED_SETS);
00097 
00098        result = stmt->result;
00099        result->type                = MYSQLND_RES_PS_BUF;
00100        result->m.fetch_row         = mysqlnd_stmt_fetch_row_buffered;
00101        result->m.fetch_lengths     = NULL;/* makes no sense */
00102 
00103        result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC);
00104 
00105        ret = result->m.store_result_fetch_data(conn, result, result->meta, TRUE, to_cache TSRMLS_CC);
00106 
00107        if (PASS == ret) {
00108               /* libmysql API docs say it should be so for SELECT statements */
00109               stmt->upsert_status.affected_rows = stmt->result->stored_data->row_count;
00110 
00111               stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
00112        } else {
00113               conn->error_info = result->stored_data->error_info;
00114               stmt->result->m.free_result_contents(stmt->result TSRMLS_CC);
00115               mnd_efree(stmt->result);
00116               stmt->result = NULL;
00117               stmt->state = MYSQLND_STMT_PREPARED;
00118        }
00119 
00120        DBG_RETURN(result);
00121 }
00122 /* }}} */
00123 
00124 
00125 /* {{{ mysqlnd_stmt::get_result */
00126 static MYSQLND_RES *
00127 MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s TSRMLS_DC)
00128 {
00129        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00130        MYSQLND * conn;
00131        MYSQLND_RES *result;
00132 
00133        DBG_ENTER("mysqlnd_stmt::get_result");
00134        if (!stmt || !stmt->conn || !stmt->result) {
00135               DBG_RETURN(NULL);
00136        }
00137        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
00138 
00139        conn = stmt->conn;
00140 
00141        /* be compliant with libmysql - NULL will turn */
00142        if (!stmt->field_count) {
00143               DBG_RETURN(NULL);
00144        }
00145 
00146        if (stmt->cursor_exists) {
00147               /* Silently convert buffered to unbuffered, for now */
00148               DBG_RETURN(s->m->use_result(s TSRMLS_CC));
00149        }
00150 
00151        /* Nothing to store for UPSERT/LOAD DATA*/
00152        if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA || stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) {
00153               SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
00154                                            UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
00155               DBG_RETURN(NULL);
00156        }
00157 
00158        SET_EMPTY_ERROR(stmt->error_info);
00159        SET_EMPTY_ERROR(stmt->conn->error_info);
00160        MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_BUFFERED_SETS);
00161 
00162        do {
00163               result = conn->m->result_init(stmt->result->field_count, stmt->persistent TSRMLS_CC);
00164               if (!result) {
00165                      SET_OOM_ERROR(stmt->conn->error_info);
00166                      break;
00167               }
00168 
00169               result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC);
00170               if (!result->meta) {
00171                      SET_OOM_ERROR(stmt->conn->error_info);
00172                      break;
00173               }
00174 
00175               if ((result = result->m.store_result(result, conn, TRUE TSRMLS_CC))) {
00176                      stmt->upsert_status.affected_rows = result->stored_data->row_count;
00177                      stmt->state = MYSQLND_STMT_PREPARED;
00178                      result->type = MYSQLND_RES_PS_BUF;
00179               } else {
00180                      stmt->error_info = conn->error_info;
00181                      stmt->state = MYSQLND_STMT_PREPARED;
00182                      break;
00183               }
00184               DBG_RETURN(result);
00185        } while (0);
00186 
00187        if (result) {
00188               result->m.free_result(result, TRUE TSRMLS_CC);
00189        }
00190        DBG_RETURN(NULL);
00191 }
00192 /* }}} */
00193 
00194 
00195 /* {{{ mysqlnd_stmt::more_results */
00196 static zend_bool
00197 MYSQLND_METHOD(mysqlnd_stmt, more_results)(const MYSQLND_STMT * s TSRMLS_DC)
00198 {
00199        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00200        DBG_ENTER("mysqlnd_stmt::more_results");
00201        /* (conn->state == CONN_NEXT_RESULT_PENDING) too */
00202        DBG_RETURN((stmt && stmt->conn && (stmt->conn->upsert_status.server_status &
00203                                                     SERVER_MORE_RESULTS_EXISTS))?
00204                                                                TRUE:
00205                                                                FALSE);
00206 }
00207 /* }}} */
00208 
00209 
00210 /* {{{ mysqlnd_stmt::next_result */
00211 static enum_func_status
00212 MYSQLND_METHOD(mysqlnd_stmt, next_result)(MYSQLND_STMT * s TSRMLS_DC)
00213 {
00214        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00215        MYSQLND * conn;
00216 
00217        DBG_ENTER("mysqlnd_stmt::next_result");
00218        if (!stmt || !stmt->conn || !stmt->result) {
00219               DBG_RETURN(FAIL);
00220        }
00221        conn = stmt->conn;
00222        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
00223 
00224        if (CONN_GET_STATE(conn) != CONN_NEXT_RESULT_PENDING || !(conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS)) {
00225               DBG_RETURN(FAIL);
00226        }
00227 
00228        DBG_INF_FMT("server_status=%u cursor=%u", stmt->upsert_status.server_status, stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS);
00229        DBG_INF_FMT("server_status=%u cursor=%u", conn->upsert_status.server_status, conn->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS);
00230 
00231        /* Free space for next result */
00232        s->m->free_stmt_content(s TSRMLS_CC);
00233        {
00234               enum_func_status ret = s->m->parse_execute_response(s TSRMLS_CC);
00235               DBG_RETURN(ret);
00236        }
00237 }
00238 /* }}} */
00239 
00240 
00241 /* {{{ mysqlnd_stmt_skip_metadata */
00242 static enum_func_status
00243 mysqlnd_stmt_skip_metadata(MYSQLND_STMT * s TSRMLS_DC)
00244 {
00245        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00246        /* Follows parameter metadata, we have just to skip it, as libmysql does */
00247        unsigned int i = 0;
00248        enum_func_status ret = FAIL;
00249        MYSQLND_PACKET_RES_FIELD * field_packet;
00250 
00251        DBG_ENTER("mysqlnd_stmt_skip_metadata");
00252        if (!stmt || !stmt->conn || !stmt->conn->protocol) {
00253               DBG_RETURN(FAIL);
00254        }
00255        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
00256 
00257        field_packet = stmt->conn->protocol->m.get_result_field_packet(stmt->conn->protocol, FALSE TSRMLS_CC);
00258        if (!field_packet) {
00259               SET_OOM_ERROR(stmt->error_info);
00260               SET_OOM_ERROR(stmt->conn->error_info);
00261        } else {
00262               ret = PASS;
00263               field_packet->skip_parsing = TRUE;
00264               for (;i < stmt->param_count; i++) {
00265                      if (FAIL == PACKET_READ(field_packet, stmt->conn)) {
00266                             ret = FAIL;
00267                             break;
00268                      }
00269               }
00270               PACKET_FREE(field_packet);
00271        }
00272 
00273        DBG_RETURN(ret);
00274 }
00275 /* }}} */
00276 
00277 
00278 /* {{{ mysqlnd_stmt_read_prepare_response */
00279 static enum_func_status
00280 mysqlnd_stmt_read_prepare_response(MYSQLND_STMT * s TSRMLS_DC)
00281 {
00282        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00283        MYSQLND_PACKET_PREPARE_RESPONSE * prepare_resp;
00284        enum_func_status ret = FAIL;
00285 
00286        DBG_ENTER("mysqlnd_stmt_read_prepare_response");
00287        if (!stmt || !stmt->conn || !stmt->conn->protocol) {
00288               DBG_RETURN(FAIL);
00289        }
00290        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
00291 
00292        prepare_resp = stmt->conn->protocol->m.get_prepare_response_packet(stmt->conn->protocol, FALSE TSRMLS_CC);
00293        if (!prepare_resp) {
00294               SET_OOM_ERROR(stmt->error_info);
00295               SET_OOM_ERROR(stmt->conn->error_info);
00296               goto done;
00297        }
00298 
00299        if (FAIL == PACKET_READ(prepare_resp, stmt->conn)) {
00300               goto done;
00301        }
00302 
00303        if (0xFF == prepare_resp->error_code) {
00304               stmt->error_info = stmt->conn->error_info = prepare_resp->error_info;
00305               goto done;
00306        }
00307        ret = PASS;
00308        stmt->stmt_id = prepare_resp->stmt_id;
00309        stmt->warning_count = stmt->conn->upsert_status.warning_count = prepare_resp->warning_count;
00310        stmt->field_count = stmt->conn->field_count = prepare_resp->field_count;
00311        stmt->param_count = prepare_resp->param_count;
00312 done:
00313        PACKET_FREE(prepare_resp);
00314 
00315        DBG_RETURN(ret);
00316 }
00317 /* }}} */
00318 
00319 
00320 /* {{{ mysqlnd_stmt_prepare_read_eof */
00321 static enum_func_status
00322 mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT * s TSRMLS_DC)
00323 {
00324        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00325        MYSQLND_PACKET_EOF * fields_eof;
00326        enum_func_status ret = FAIL;
00327 
00328        DBG_ENTER("mysqlnd_stmt_prepare_read_eof");
00329        if (!stmt || !stmt->conn || !stmt->conn->protocol) {
00330               DBG_RETURN(FAIL);
00331        }
00332        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
00333 
00334        fields_eof = stmt->conn->protocol->m.get_eof_packet(stmt->conn->protocol, FALSE TSRMLS_CC);
00335        if (!fields_eof) {
00336               SET_OOM_ERROR(stmt->error_info);
00337               SET_OOM_ERROR(stmt->conn->error_info);
00338        } else {
00339               if (FAIL == (ret = PACKET_READ(fields_eof, stmt->conn))) {
00340                      if (stmt->result) {
00341                             stmt->result->m.free_result_contents(stmt->result TSRMLS_CC);
00342                             mnd_efree(stmt->result);
00343                             memset(stmt, 0, sizeof(MYSQLND_STMT_DATA));
00344                             stmt->state = MYSQLND_STMT_INITTED;
00345                      }
00346               } else {
00347                      stmt->upsert_status.server_status = fields_eof->server_status;
00348                      stmt->upsert_status.warning_count = fields_eof->warning_count;
00349                      stmt->state = MYSQLND_STMT_PREPARED;
00350               }
00351               PACKET_FREE(fields_eof);
00352        }
00353 
00354        DBG_RETURN(ret);
00355 }
00356 /* }}} */
00357 
00358 
00359 /* {{{ mysqlnd_stmt::prepare */
00360 static enum_func_status
00361 MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const query, unsigned int query_len TSRMLS_DC)
00362 {
00363        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00364        MYSQLND_STMT * s_to_prepare = s;
00365        MYSQLND_STMT_DATA * stmt_to_prepare = stmt;
00366 
00367        DBG_ENTER("mysqlnd_stmt::prepare");
00368        if (!stmt || !stmt->conn) {
00369               DBG_RETURN(FAIL);
00370        }
00371        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
00372 
00373        SET_ERROR_AFF_ROWS(stmt);
00374        SET_ERROR_AFF_ROWS(stmt->conn);
00375 
00376        SET_EMPTY_ERROR(stmt->error_info);
00377        SET_EMPTY_ERROR(stmt->conn->error_info);
00378 
00379        if (stmt->state > MYSQLND_STMT_INITTED) {
00380               /* See if we have to clean the wire */
00381               if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
00382                      /* Do implicit use_result and then flush the result */
00383                      stmt->default_rset_handler = s->m->use_result;
00384                      stmt->default_rset_handler(s TSRMLS_CC);
00385               }
00386               /* No 'else' here please :) */
00387               if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE && stmt->result) {
00388                      stmt->result->m.skip_result(stmt->result TSRMLS_CC);
00389               }
00390               /*
00391                 Create a new test statement, which we will prepare, but if anything
00392                 fails, we will scrap it.
00393               */
00394               s_to_prepare = stmt->conn->m->stmt_init(stmt->conn TSRMLS_CC);
00395               if (!s_to_prepare) {
00396                      goto fail;
00397               }
00398               stmt_to_prepare = s_to_prepare->data;
00399        }
00400 
00401        if (FAIL == stmt_to_prepare->conn->m->simple_command(stmt_to_prepare->conn, COM_STMT_PREPARE, query, query_len, PROT_LAST, FALSE, TRUE TSRMLS_CC) ||
00402               FAIL == mysqlnd_stmt_read_prepare_response(s_to_prepare TSRMLS_CC))
00403        {
00404               goto fail;
00405        }
00406 
00407        if (stmt_to_prepare->param_count) {
00408               if (FAIL == mysqlnd_stmt_skip_metadata(s_to_prepare TSRMLS_CC) ||
00409                      FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare TSRMLS_CC))
00410               {
00411                      goto fail;
00412               }
00413        }
00414 
00415        /*
00416          Read metadata only if there is actual result set.
00417          Beware that SHOW statements bypass the PS framework and thus they send
00418          no metadata at prepare.
00419        */
00420        if (stmt_to_prepare->field_count) {
00421               MYSQLND_RES * result = stmt->conn->m->result_init(stmt_to_prepare->field_count, stmt_to_prepare->persistent TSRMLS_CC);
00422               if (!result) {
00423                      SET_OOM_ERROR(stmt->conn->error_info);
00424                      goto fail;
00425               }
00426               /* Allocate the result now as it is needed for the reading of metadata */
00427               stmt_to_prepare->result = result; 
00428 
00429               result->conn = stmt_to_prepare->conn->m->get_reference(stmt_to_prepare->conn TSRMLS_CC);
00430 
00431               result->type = MYSQLND_RES_PS_BUF;
00432 
00433               if (FAIL == result->m.read_result_metadata(result, stmt_to_prepare->conn TSRMLS_CC) ||
00434                      FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare TSRMLS_CC))
00435               {
00436                      goto fail;
00437               }
00438        }
00439 
00440        if (stmt_to_prepare != stmt) {
00441               /* swap */
00442               size_t real_size = sizeof(MYSQLND_STMT) + mysqlnd_plugin_count() * sizeof(void *);
00443               char * tmp_swap = mnd_malloc(real_size);
00444               memcpy(tmp_swap, s, real_size);
00445               memcpy(s, s_to_prepare, real_size);
00446               memcpy(s_to_prepare, tmp_swap, real_size);
00447               mnd_free(tmp_swap);
00448               {
00449                      MYSQLND_STMT_DATA * tmp_swap_data = stmt_to_prepare;
00450                      stmt_to_prepare = stmt;
00451                      stmt = tmp_swap_data;
00452               }
00453               s_to_prepare->m->dtor(s_to_prepare, TRUE TSRMLS_CC);
00454        }
00455        stmt->state = MYSQLND_STMT_PREPARED;
00456        DBG_INF("PASS");
00457        DBG_RETURN(PASS);
00458 
00459 fail:
00460        if (stmt_to_prepare != stmt && s_to_prepare) {
00461               s_to_prepare->m->dtor(s_to_prepare, TRUE TSRMLS_CC);
00462        }
00463        stmt->state = MYSQLND_STMT_INITTED;
00464 
00465        DBG_INF("FAIL");
00466        DBG_RETURN(FAIL);
00467 }
00468 /* }}} */
00469 
00470 
00471 /* {{{ mysqlnd_stmt_execute_parse_response */
00472 static enum_func_status
00473 mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s TSRMLS_DC)
00474 {
00475        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00476        enum_func_status ret;
00477        MYSQLND       * conn;
00478 
00479        DBG_ENTER("mysqlnd_stmt_execute_parse_response");
00480        if (!stmt || !stmt->conn) {
00481               DBG_RETURN(FAIL);
00482        }
00483        conn = stmt->conn;
00484        CONN_SET_STATE(conn, CONN_QUERY_SENT);
00485 
00486        ret = mysqlnd_query_read_result_set_header(stmt->conn, s TSRMLS_CC);
00487        if (ret == FAIL) {
00488               stmt->error_info = conn->error_info;
00489               stmt->upsert_status.affected_rows = conn->upsert_status.affected_rows;
00490               if (CONN_GET_STATE(conn) == CONN_QUIT_SENT) {
00491                      /* close the statement here, the connection has been closed */
00492               }
00493               stmt->state = MYSQLND_STMT_PREPARED;
00494               stmt->send_types_to_server = 1;
00495        } else {
00496               /*
00497                 stmt->send_types_to_server has already been set to 0 in
00498                 mysqlnd_stmt_execute_generate_request / mysqlnd_stmt_execute_store_params
00499                 In case there is a situation in which binding was done for integer and the
00500                 value is > LONG_MAX or < LONG_MIN, there is string conversion and we have
00501                 to resend the types. Next execution will also need to resend the type.
00502               */
00503               SET_EMPTY_ERROR(stmt->error_info);
00504               SET_EMPTY_ERROR(stmt->conn->error_info);
00505               stmt->upsert_status = conn->upsert_status;
00506               stmt->state = MYSQLND_STMT_EXECUTED;
00507               if (conn->last_query_type == QUERY_UPSERT || conn->last_query_type == QUERY_LOAD_LOCAL) {
00508                      DBG_INF("PASS");
00509                      DBG_RETURN(PASS);
00510               }
00511 
00512               stmt->result->type = MYSQLND_RES_PS_BUF;
00513               if (!stmt->result->conn) {
00514                      /*
00515                        For SHOW we don't create (bypasses PS in server)
00516                        a result set at prepare and thus a connection was missing
00517                      */
00518                      stmt->result->conn = stmt->conn->m->get_reference(stmt->conn TSRMLS_CC);
00519               }
00520 
00521               /* Update stmt->field_count as SHOW sets it to 0 at prepare */
00522               stmt->field_count = stmt->result->field_count = conn->field_count;
00523               stmt->result->lengths = NULL;
00524               if (stmt->field_count) {
00525                      stmt->state = MYSQLND_STMT_WAITING_USE_OR_STORE;
00526                      /*
00527                        We need to set this because the user might not call
00528                        use_result() or store_result() and we should be able to scrap the
00529                        data on the line, if he just decides to close the statement.
00530                      */
00531                      DBG_INF_FMT("server_status=%u cursor=%u", stmt->upsert_status.server_status,
00532                                           stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS);
00533 
00534                      if (stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS) {
00535                             DBG_INF("cursor exists");
00536                             stmt->cursor_exists = TRUE;
00537                             CONN_SET_STATE(conn, CONN_READY);
00538                             /* Only cursor read */
00539                             stmt->default_rset_handler = s->m->use_result;
00540                             DBG_INF("use_result");
00541                      } else if (stmt->flags & CURSOR_TYPE_READ_ONLY) {
00542                             DBG_INF("asked for cursor but got none");
00543                             /*
00544                               We have asked for CURSOR but got no cursor, because the condition
00545                               above is not fulfilled. Then...
00546 
00547                               This is a single-row result set, a result set with no rows, EXPLAIN,
00548                               SHOW VARIABLES, or some other command which either a) bypasses the
00549                               cursors framework in the server and writes rows directly to the
00550                               network or b) is more efficient if all (few) result set rows are
00551                               precached on client and server's resources are freed.
00552                             */
00553                             /* preferred is buffered read */
00554                             stmt->default_rset_handler = s->m->store_result;
00555                             DBG_INF("store_result");
00556                      } else {
00557                             DBG_INF("no cursor");
00558                             /* preferred is unbuffered read */
00559                             stmt->default_rset_handler = s->m->use_result;
00560                             DBG_INF("use_result");
00561                      }
00562               }
00563        }
00564 #ifndef MYSQLND_DONT_SKIP_OUT_PARAMS_RESULTSET
00565        if (stmt->upsert_status.server_status & SERVER_PS_OUT_PARAMS) {
00566               s->m->free_stmt_content(s TSRMLS_CC);
00567               DBG_INF("PS OUT Variable RSet, skipping");
00568               /* OUT params result set. Skip for now to retain compatibility */
00569               ret = mysqlnd_stmt_execute_parse_response(s TSRMLS_CC);
00570        }
00571 #endif
00572 
00573        DBG_INF(ret == PASS? "PASS":"FAIL");
00574        DBG_RETURN(ret);
00575 }
00576 /* }}} */
00577 
00578 
00579 /* {{{ mysqlnd_stmt::execute */
00580 static enum_func_status
00581 MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const s TSRMLS_DC)
00582 {
00583        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00584        enum_func_status ret;
00585        MYSQLND *     conn;
00586        zend_uchar *request = NULL;
00587        size_t        request_len;
00588        zend_bool     free_request;
00589 
00590        DBG_ENTER("mysqlnd_stmt::execute");
00591        if (!stmt || !stmt->conn) {
00592               DBG_RETURN(FAIL);
00593        }
00594        conn = stmt->conn;
00595        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
00596 
00597        SET_ERROR_AFF_ROWS(stmt);
00598        SET_ERROR_AFF_ROWS(stmt->conn);
00599 
00600        if (stmt->result && stmt->state >= MYSQLND_STMT_PREPARED && stmt->field_count) {
00601               /*
00602                 We don need to copy the data from the buffers which we will clean.
00603                 Because it has already been copied. See
00604                 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
00605               */
00606 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
00607               if (stmt->result_bind &&
00608                      stmt->result_zvals_separated_once == TRUE && 
00609                      stmt->state >= MYSQLND_STMT_USER_FETCHING)
00610               {
00611                      /*
00612                        We need to copy the data from the buffers which we will clean.
00613                        The bound variables point to them only if the user has started
00614                        to fetch data (MYSQLND_STMT_USER_FETCHING).
00615                        We need to check 'result_zvals_separated_once' or we will leak
00616                        in the following scenario
00617                        prepare("select 1 from dual");
00618                        execute();
00619                        fetch(); <-- no binding, but that's not a problem
00620                        bind_result();
00621                        execute(); <-- here we will leak because we separate without need
00622                      */
00623                      unsigned int i;
00624                      for (i = 0; i < stmt->field_count; i++) {
00625                             if (stmt->result_bind[i].bound == TRUE) {
00626                                    zval_copy_ctor(stmt->result_bind[i].zv);
00627                             }
00628                      }
00629               }
00630 #endif
00631 
00632               /*
00633                 If right after execute() we have to call the appropriate
00634                 use_result() or store_result() and clean.
00635               */
00636               if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
00637                      DBG_INF("fetching result set header");
00638                      /* Do implicit use_result and then flush the result */
00639                      stmt->default_rset_handler = s->m->use_result;
00640                      stmt->default_rset_handler(s TSRMLS_CC);
00641               }
00642 
00643               if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
00644                      DBG_INF("skipping result");
00645                      /* Flush if anything is left and unbuffered set */
00646                      stmt->result->m.skip_result(stmt->result TSRMLS_CC);
00647               }
00648 
00649               if (stmt->state > MYSQLND_STMT_PREPARED) {
00650                      /* As the buffers have been freed, we should go back to PREPARED */
00651                      stmt->state = MYSQLND_STMT_PREPARED;
00652               }
00653 
00654               /*
00655                 Executed, but the user hasn't started to fetch
00656                 This will clean also the metadata, but after the EXECUTE call we will
00657                 have it again.
00658               */
00659               stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC);
00660        } else if (stmt->state < MYSQLND_STMT_PREPARED) {
00661               /* Only initted - error */
00662               SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
00663                                            mysqlnd_out_of_sync);
00664               SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
00665               DBG_INF("FAIL");
00666               DBG_RETURN(FAIL);
00667        }
00668 
00669        if (stmt->param_count) {
00670               unsigned int i, not_bound = 0;
00671               if (!stmt->param_bind) {
00672                      SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE,
00673                                                   "No data supplied for parameters in prepared statement");
00674                      DBG_INF("FAIL");
00675                      DBG_RETURN(FAIL);
00676               }
00677               for (i = 0; i < stmt->param_count; i++) {
00678                      if (stmt->param_bind[i].zv == NULL) {
00679                             not_bound++;
00680                      }
00681               }
00682               if (not_bound) {
00683                      char * msg;
00684                      spprintf(&msg, 0, "No data supplied for %u parameter%s in prepared statement",
00685                                     not_bound, not_bound>1 ?"s":"");
00686                      SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, msg);
00687                      if (msg) {
00688                             efree(msg); /* allocated by spprintf */
00689                      }
00690                      DBG_INF("FAIL");
00691                      DBG_RETURN(FAIL);
00692               }
00693        }
00694        ret = s->m->generate_execute_request(s, &request, &request_len, &free_request TSRMLS_CC);
00695        if (ret == PASS) {
00696               /* support for buffer types should be added here ! */
00697               ret = stmt->conn->m->simple_command(stmt->conn, COM_STMT_EXECUTE, (char *)request, request_len,
00698                                                                              PROT_LAST /* we will handle the response packet*/,
00699                                                                              FALSE, FALSE TSRMLS_CC);
00700        } else {
00701               SET_STMT_ERROR(stmt, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Couldn't generate the request. Possibly OOM.");
00702        }
00703 
00704        if (free_request) {
00705               mnd_efree(request);
00706        }
00707 
00708        if (ret == FAIL) {
00709               stmt->error_info = conn->error_info;
00710               DBG_INF("FAIL");
00711               DBG_RETURN(FAIL);
00712        }
00713        stmt->execute_count++;
00714 
00715        ret = s->m->parse_execute_response(s TSRMLS_CC);
00716 
00717        DBG_INF_FMT("server_status=%u cursor=%u", stmt->upsert_status.server_status, stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS);
00718 
00719        if (ret == PASS && conn->last_query_type == QUERY_UPSERT && stmt->upsert_status.affected_rows) {
00720               MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_ROWS_AFFECTED_PS, stmt->upsert_status.affected_rows);
00721        }
00722        DBG_RETURN(ret);
00723 }
00724 /* }}} */
00725 
00726 
00727 /* {{{ mysqlnd_stmt_fetch_row_buffered */
00728 enum_func_status
00729 mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
00730 {
00731        MYSQLND_STMT * s = (MYSQLND_STMT *) param;
00732        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00733        MYSQLND_RES_BUFFERED *set = result->stored_data;
00734        unsigned int field_count = result->meta->field_count;
00735 
00736        DBG_ENTER("mysqlnd_stmt_fetch_row_buffered");
00737        *fetched_anything = FALSE;
00738        DBG_INF_FMT("stmt=%lu", stmt != NULL ? stmt->stmt_id : 0L);
00739 
00740        /* If we haven't read everything */
00741        if (set->data_cursor &&
00742               (set->data_cursor - set->data) < (set->row_count * field_count))
00743        {
00744               /* The user could have skipped binding - don't crash*/
00745               if (stmt->result_bind) {
00746                      unsigned int i;
00747                      MYSQLND_RES_METADATA * meta = result->meta;
00748                      zval **current_row = set->data_cursor;
00749 
00750                      if (NULL == current_row[0]) {
00751                             uint64_t row_num = (set->data_cursor - set->data) / field_count;
00752                             enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num],
00753                                                                                     current_row,
00754                                                                                     meta->field_count,
00755                                                                                     meta->fields,
00756                                                                                     result->stored_data->persistent,
00757                                                                                     result->conn->options.numeric_and_datetime_as_unicode,
00758                                                                                     result->conn->options.int_and_float_native,
00759                                                                                     result->conn->stats TSRMLS_CC);
00760                             if (PASS != rc) {
00761                                    DBG_RETURN(FAIL);
00762                             }
00763                             set->initialized_rows++;
00764                             if (stmt->update_max_length) {
00765                                    for (i = 0; i < result->field_count; i++) {
00766                                           /*
00767                                             NULL fields are 0 length, 0 is not more than 0
00768                                             String of zero size, definitely can't be the next max_length.
00769                                             Thus for NULL and zero-length we are quite efficient.
00770                                           */
00771                                           if (Z_TYPE_P(current_row[i]) >= IS_STRING) {
00772                                                  unsigned long len = Z_STRLEN_P(current_row[i]);
00773                                                  if (meta->fields[i].max_length < len) {
00774                                                         meta->fields[i].max_length = len;
00775                                                  }
00776                                           }
00777                                    }
00778                             }
00779                      }
00780 
00781                      for (i = 0; i < result->field_count; i++) {
00782                             /* Clean what we copied last time */
00783 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
00784                             if (stmt->result_bind[i].zv) {
00785                                    zval_dtor(stmt->result_bind[i].zv);
00786                             }
00787 #endif
00788                             /* copy the type */
00789                             if (stmt->result_bind[i].bound == TRUE) {
00790                                    DBG_INF_FMT("i=%u type=%u", i, Z_TYPE_P(current_row[i]));
00791                                    if (Z_TYPE_P(current_row[i]) != IS_NULL) {
00792                                           /*
00793                                             Copy the value.
00794                                             Pre-condition is that the zvals in the result_bind buffer
00795                                             have been  ZVAL_NULL()-ed or to another simple type
00796                                             (int, double, bool but not string). Because of the reference
00797                                             counting the user can't delete the strings the variables point to.
00798                                           */
00799 
00800                                           Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]);
00801                                           stmt->result_bind[i].zv->value = current_row[i]->value;
00802 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
00803                                           zval_copy_ctor(stmt->result_bind[i].zv);
00804 #endif
00805                                    } else {
00806                                           ZVAL_NULL(stmt->result_bind[i].zv);
00807                                    }
00808                             }
00809                      }
00810               }
00811               set->data_cursor += field_count;
00812               *fetched_anything = TRUE;
00813               /* buffered result sets don't have a connection */
00814               MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF);
00815               DBG_INF("row fetched");
00816        } else {
00817               set->data_cursor = NULL;
00818               DBG_INF("no more data");
00819        }
00820        DBG_INF("PASS");
00821        DBG_RETURN(PASS);
00822 }
00823 /* }}} */
00824 
00825 
00826 /* {{{ mysqlnd_stmt_fetch_row_unbuffered */
00827 static enum_func_status
00828 mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
00829 {
00830        enum_func_status ret;
00831        MYSQLND_STMT * s = (MYSQLND_STMT *) param;
00832        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00833        MYSQLND_PACKET_ROW * row_packet;
00834 
00835        DBG_ENTER("mysqlnd_stmt_fetch_row_unbuffered");
00836 
00837        *fetched_anything = FALSE;
00838 
00839        if (result->unbuf->eof_reached) {
00840               /* No more rows obviously */
00841               DBG_INF("eof reached");
00842               DBG_RETURN(PASS);
00843        }
00844        if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) {
00845               SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
00846                                            UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
00847               DBG_ERR("command out of sync");
00848               DBG_RETURN(FAIL);
00849        }
00850        if (!(row_packet = result->row_packet)) {
00851               DBG_RETURN(FAIL);
00852        }
00853 
00854        /* Let the row packet fill our buffer and skip additional malloc + memcpy */
00855        row_packet->skip_extraction = stmt && stmt->result_bind? FALSE:TRUE;
00856 
00857        /*
00858          If we skip rows (stmt == NULL || stmt->result_bind == NULL) we have to
00859          result->m.unbuffered_free_last_data() before it. The function returns always true.
00860        */
00861        if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
00862               unsigned int i, field_count = result->field_count;
00863 
00864               if (!row_packet->skip_extraction) {
00865                      result->m.unbuffered_free_last_data(result TSRMLS_CC);
00866 
00867                      DBG_INF("extracting data");
00868                      result->unbuf->last_row_data = row_packet->fields;
00869                      result->unbuf->last_row_buffer = row_packet->row_buffer;
00870                      row_packet->fields = NULL;
00871                      row_packet->row_buffer = NULL;
00872 
00873                      if (PASS != result->m.row_decoder(result->unbuf->last_row_buffer,
00874                                                                result->unbuf->last_row_data,
00875                                                                row_packet->field_count,
00876                                                                row_packet->fields_metadata,
00877                                                                FALSE,
00878                                                                result->conn->options.numeric_and_datetime_as_unicode,
00879                                                                result->conn->options.int_and_float_native,
00880                                                                result->conn->stats TSRMLS_CC))
00881                      {
00882                             DBG_RETURN(FAIL);
00883                      }
00884 
00885                      for (i = 0; i < field_count; i++) {
00886                             if (stmt->result_bind[i].bound == TRUE) {
00887                                    zval *data = result->unbuf->last_row_data[i];
00888                                    /*
00889                                      stmt->result_bind[i].zv has been already destructed
00890                                      in result->m.unbuffered_free_last_data()
00891                                    */
00892 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
00893                                    zval_dtor(stmt->result_bind[i].zv);
00894 #endif
00895                                    if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) {
00896                                           if (
00897                                                  (Z_TYPE_P(data) == IS_STRING
00898 #if MYSQLND_UNICODE
00899                                                  || Z_TYPE_P(data) == IS_UNICODE
00900 #endif
00901                                                  )
00902                                                   && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data)))
00903                                           {
00904                                                  result->meta->fields[i].max_length = Z_STRLEN_P(data);
00905                                           }
00906                                           stmt->result_bind[i].zv->value = data->value;
00907                                           /* copied data, thus also the ownership. Thus null data */
00908                                           ZVAL_NULL(data);
00909                                    }
00910                             }
00911                      }
00912                      MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_UNBUF);
00913               } else {
00914                      DBG_INF("skipping extraction");
00915                      /*
00916                        Data has been allocated and usually result->m.unbuffered_free_last_data()
00917                        frees it but we can't call this function as it will cause problems with
00918                        the bound variables. Thus we need to do part of what it does or Zend will
00919                        report leaks.
00920                      */
00921                      row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC);
00922                      row_packet->row_buffer = NULL;
00923               }
00924 
00925               result->unbuf->row_count++;
00926               *fetched_anything = TRUE;
00927        } else if (ret == FAIL) {
00928               if (row_packet->error_info.error_no) {
00929                      stmt->conn->error_info = row_packet->error_info; 
00930                      stmt->error_info = row_packet->error_info; 
00931               }
00932               CONN_SET_STATE(result->conn, CONN_READY);
00933               result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */
00934        } else if (row_packet->eof) {
00935               DBG_INF("EOF");
00936               /* Mark the connection as usable again */
00937               result->unbuf->eof_reached = TRUE;
00938               result->conn->upsert_status.warning_count = row_packet->warning_count;
00939               result->conn->upsert_status.server_status = row_packet->server_status;
00940               /*
00941                 result->row_packet will be cleaned when
00942                 destroying the result object
00943               */
00944               if (result->conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) {
00945                      CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING);
00946               } else {
00947                      CONN_SET_STATE(result->conn, CONN_READY);
00948               }
00949        }
00950 
00951        DBG_INF_FMT("ret=%s fetched_anything=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
00952        DBG_RETURN(ret);
00953 }
00954 /* }}} */
00955 
00956 
00957 /* {{{ mysqlnd_stmt::use_result */
00958 static MYSQLND_RES *
00959 MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s TSRMLS_DC)
00960 {
00961        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
00962        MYSQLND_RES *result;
00963        MYSQLND * conn;
00964 
00965        DBG_ENTER("mysqlnd_stmt::use_result");
00966        if (!stmt || !stmt->conn || !stmt->result) {
00967               DBG_RETURN(NULL);
00968        }
00969        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
00970 
00971        conn = stmt->conn;
00972 
00973        if (!stmt->field_count ||
00974               (!stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_FETCHING_DATA) ||
00975               (stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_READY) ||
00976               (stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE))
00977        {
00978               SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC,
00979                                            UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
00980               DBG_ERR("command out of sync");
00981               DBG_RETURN(NULL);
00982        }
00983 
00984        SET_EMPTY_ERROR(stmt->error_info);
00985 
00986        MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_PS_UNBUFFERED_SETS);
00987        result = stmt->result;
00988 
00989        DBG_INF_FMT("%scursor exists", stmt->cursor_exists? "":"no ");
00990        result->m.use_result(stmt->result, TRUE TSRMLS_CC);
00991        result->m.fetch_row  = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor:
00992                                                                                 mysqlnd_stmt_fetch_row_unbuffered;
00993        stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
00994 
00995        DBG_INF_FMT("%p", result);
00996        DBG_RETURN(result);
00997 }
00998 /* }}} */
00999 
01000 
01001 #define STMT_ID_LENGTH 4
01002 
01003 /* {{{ mysqlnd_fetch_row_cursor */
01004 enum_func_status
01005 mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC)
01006 {
01007        enum_func_status ret;
01008        MYSQLND_STMT * s = (MYSQLND_STMT *) param;
01009        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01010        zend_uchar buf[STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */];
01011        MYSQLND_PACKET_ROW * row_packet;
01012 
01013        DBG_ENTER("mysqlnd_fetch_stmt_row_cursor");
01014 
01015        if (!stmt || !stmt->conn || !result || !result->conn || !result->unbuf) {
01016               DBG_ERR("no statement");
01017               DBG_RETURN(FAIL);
01018        }
01019 
01020        DBG_INF_FMT("stmt=%lu flags=%u", stmt->stmt_id, flags);
01021 
01022        if (stmt->state < MYSQLND_STMT_USER_FETCHING) {
01023               /* Only initted - error */
01024               SET_CLIENT_ERROR(stmt->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
01025                                           mysqlnd_out_of_sync);
01026               DBG_ERR("command out of sync");
01027               DBG_RETURN(FAIL);
01028        }
01029        if (!(row_packet = result->row_packet)) {
01030               DBG_RETURN(FAIL);
01031        }
01032 
01033        SET_EMPTY_ERROR(stmt->error_info);
01034        SET_EMPTY_ERROR(stmt->conn->error_info);
01035 
01036        int4store(buf, stmt->stmt_id);
01037        int4store(buf + STMT_ID_LENGTH, 1); /* for now fetch only one row */
01038 
01039        if (FAIL == stmt->conn->m->simple_command(stmt->conn, COM_STMT_FETCH, (char *)buf, sizeof(buf),
01040                                                                                PROT_LAST /* we will handle the response packet*/,
01041                                                                                FALSE, TRUE TSRMLS_CC)) {
01042               stmt->error_info = stmt->conn->error_info;
01043               DBG_RETURN(FAIL);
01044        }
01045 
01046        row_packet->skip_extraction = stmt->result_bind? FALSE:TRUE;
01047 
01048        if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) {
01049               unsigned int i, field_count = result->field_count;
01050 
01051               DBG_INF_FMT("skip_extraction=%u", row_packet->skip_extraction); 
01052               if (!row_packet->skip_extraction) {
01053                      result->m.unbuffered_free_last_data(result TSRMLS_CC);
01054 
01055                      DBG_INF("extracting data");
01056                      result->unbuf->last_row_data = row_packet->fields;
01057                      result->unbuf->last_row_buffer = row_packet->row_buffer;
01058                      row_packet->fields = NULL;
01059                      row_packet->row_buffer = NULL;
01060 
01061                      if (PASS != result->m.row_decoder(result->unbuf->last_row_buffer,
01062                                                                  result->unbuf->last_row_data,
01063                                                                  row_packet->field_count,
01064                                                                  row_packet->fields_metadata,
01065                                                                  FALSE,
01066                                                                  result->conn->options.numeric_and_datetime_as_unicode,
01067                                                                  result->conn->options.int_and_float_native,
01068                                                                  result->conn->stats TSRMLS_CC))
01069                      {
01070                             DBG_RETURN(FAIL);                                         
01071                      }
01072 
01073                      /* If no result bind, do nothing. We consumed the data */
01074                      for (i = 0; i < field_count; i++) {
01075                             if (stmt->result_bind[i].bound == TRUE) {
01076                                    zval *data = result->unbuf->last_row_data[i];
01077                                    /*
01078                                      stmt->result_bind[i].zv has been already destructed
01079                                      in result->m.unbuffered_free_last_data()
01080                                    */
01081 #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
01082                                    zval_dtor(stmt->result_bind[i].zv);
01083 #endif
01084                                    DBG_INF_FMT("i=%u bound_var=%p type=%u refc=%u", i, stmt->result_bind[i].zv,
01085                                                         Z_TYPE_P(data), Z_REFCOUNT_P(stmt->result_bind[i].zv));
01086                                    if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data))) {
01087                                           if ((Z_TYPE_P(data) == IS_STRING
01088 #if MYSQLND_UNICODE
01089                                                  || Z_TYPE_P(data) == IS_UNICODE
01090 #endif
01091                                                  )
01092                                                   && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data)))
01093                                           {
01094                                                  result->meta->fields[i].max_length = Z_STRLEN_P(data);
01095                                           }
01096                                           stmt->result_bind[i].zv->value = data->value;
01097                                           /* copied data, thus also the ownership. Thus null data */
01098                                           ZVAL_NULL(data);
01099                                    }
01100                             }
01101                      }
01102               } else {
01103                      DBG_INF("skipping extraction");
01104                      /*
01105                        Data has been allocated and usually result->m.unbuffered_free_last_data()
01106                        frees it but we can't call this function as it will cause problems with
01107                        the bound variables. Thus we need to do part of what it does or Zend will
01108                        report leaks.
01109                      */
01110                      row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC);
01111                      row_packet->row_buffer = NULL;
01112               }
01113               /* We asked for one row, the next one should be EOF, eat it */
01114               ret = PACKET_READ(row_packet, result->conn);
01115               if (row_packet->row_buffer) {
01116                      row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC);
01117                      row_packet->row_buffer = NULL;
01118               }
01119               MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR);
01120 
01121               result->unbuf->row_count++;
01122               *fetched_anything = TRUE;
01123        } else {
01124               *fetched_anything = FALSE;
01125 
01126               stmt->upsert_status.warning_count =
01127                      stmt->conn->upsert_status.warning_count =
01128                             row_packet->warning_count;
01129 
01130               stmt->upsert_status.server_status = 
01131                      stmt->conn->upsert_status.server_status =
01132                             row_packet->server_status;
01133 
01134               result->unbuf->eof_reached = row_packet->eof;
01135        }
01136        stmt->upsert_status.warning_count =
01137               stmt->conn->upsert_status.warning_count =
01138                      row_packet->warning_count;
01139        stmt->upsert_status.server_status = 
01140               stmt->conn->upsert_status.server_status =
01141                      row_packet->server_status;
01142 
01143        DBG_INF_FMT("ret=%s fetched=%u server_status=%u warnings=%u eof=%u",
01144                             ret == PASS? "PASS":"FAIL", *fetched_anything,
01145                             row_packet->server_status, row_packet->warning_count,
01146                             result->unbuf->eof_reached);
01147        DBG_RETURN(ret);
01148 }
01149 /* }}} */
01150 
01151 
01152 /* {{{ mysqlnd_stmt::fetch */
01153 static enum_func_status
01154 MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const s, zend_bool * const fetched_anything TSRMLS_DC)
01155 {
01156        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01157        enum_func_status ret;
01158        DBG_ENTER("mysqlnd_stmt::fetch");
01159        if (!stmt || !stmt->conn) {
01160               DBG_RETURN(FAIL);
01161        }
01162        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
01163 
01164        if (!stmt->result ||
01165               stmt->state < MYSQLND_STMT_WAITING_USE_OR_STORE) {
01166               SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
01167 
01168               DBG_ERR("command out of sync");
01169               DBG_RETURN(FAIL);
01170        } else if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
01171               /* Execute only once. We have to free the previous contents of user's bound vars */
01172 
01173               stmt->default_rset_handler(s TSRMLS_CC);
01174        }
01175        stmt->state = MYSQLND_STMT_USER_FETCHING;
01176 
01177        SET_EMPTY_ERROR(stmt->error_info);
01178        SET_EMPTY_ERROR(stmt->conn->error_info);
01179 
01180        DBG_INF_FMT("result_bind=%p separated_once=%u", stmt->result_bind, stmt->result_zvals_separated_once);
01181        /*
01182          The user might have not bound any variables for result.
01183          Do the binding once she does it.
01184        */
01185        if (stmt->result_bind && !stmt->result_zvals_separated_once) {
01186               unsigned int i;
01187               /*
01188                 mysqlnd_stmt_store_result() has been called free the bind
01189                 variables to prevent leaking of their previous content.
01190               */
01191               for (i = 0; i < stmt->result->field_count; i++) {
01192                      if (stmt->result_bind[i].bound == TRUE) {
01193                             zval_dtor(stmt->result_bind[i].zv);
01194                             ZVAL_NULL(stmt->result_bind[i].zv);
01195                      }
01196               }
01197               stmt->result_zvals_separated_once = TRUE;
01198        }
01199 
01200        ret = stmt->result->m.fetch_row(stmt->result, (void*)s, 0, fetched_anything TSRMLS_CC);
01201        DBG_RETURN(ret);
01202 }
01203 /* }}} */
01204 
01205 
01206 /* {{{ mysqlnd_stmt::reset */
01207 static enum_func_status
01208 MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const s TSRMLS_DC)
01209 {
01210        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01211        enum_func_status ret = PASS;
01212        zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */];
01213 
01214        DBG_ENTER("mysqlnd_stmt::reset");
01215        if (!stmt || !stmt->conn) {
01216               DBG_RETURN(FAIL);
01217        }
01218        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
01219 
01220        SET_EMPTY_ERROR(stmt->error_info);
01221        SET_EMPTY_ERROR(stmt->conn->error_info);
01222 
01223        if (stmt->stmt_id) {
01224               MYSQLND * conn = stmt->conn;
01225               if (stmt->param_bind) {
01226                      unsigned int i;
01227                      DBG_INF("resetting long data");
01228                      /* Reset Long Data */
01229                      for (i = 0; i < stmt->param_count; i++) {
01230                             if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
01231                                    stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
01232                             }
01233                      }
01234               }
01235 
01236               /*
01237                 If the user decided to close the statement right after execute()
01238                 We have to call the appropriate use_result() or store_result() and
01239                 clean.
01240               */
01241               do {
01242                      if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
01243                             DBG_INF("fetching result set header");
01244                             stmt->default_rset_handler(s TSRMLS_CC);
01245                             stmt->state = MYSQLND_STMT_USER_FETCHING;
01246                      }
01247 
01248                      if (stmt->result) {
01249                             DBG_INF("skipping result");
01250                             stmt->result->m.skip_result(stmt->result TSRMLS_CC);
01251                      }
01252               } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
01253 
01254               /*
01255                 Don't free now, let the result be usable. When the stmt will again be
01256                 executed then the result set will be cleaned, the bound variables will
01257                 be separated before that.
01258               */
01259 
01260               int4store(cmd_buf, stmt->stmt_id);
01261               if (CONN_GET_STATE(conn) == CONN_READY &&
01262                      FAIL == (ret = conn->m->simple_command(conn, COM_STMT_RESET, (char *)cmd_buf,
01263                                                                                       sizeof(cmd_buf), PROT_OK_PACKET,
01264                                                                                       FALSE, TRUE TSRMLS_CC))) {
01265                      stmt->error_info = conn->error_info;
01266               }
01267               stmt->upsert_status = conn->upsert_status;
01268 
01269               stmt->state = MYSQLND_STMT_PREPARED;
01270        }
01271        DBG_INF(ret == PASS? "PASS":"FAIL");
01272        DBG_RETURN(ret);
01273 }
01274 /* }}} */
01275 
01276 
01277 /* {{{ mysqlnd_stmt::send_long_data */
01278 static enum_func_status
01279 MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const s, unsigned int param_no,
01280                                                                               const char * const data, unsigned long length TSRMLS_DC)
01281 {
01282        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01283        enum_func_status ret = FAIL;
01284        MYSQLND * conn;
01285        zend_uchar *cmd_buf;
01286        enum php_mysqlnd_server_command cmd = COM_STMT_SEND_LONG_DATA;
01287 
01288        DBG_ENTER("mysqlnd_stmt::send_long_data");
01289        if (!stmt || !stmt->conn) {
01290               DBG_RETURN(FAIL);
01291        }
01292        DBG_INF_FMT("stmt=%lu param_no=%u data_len=%lu", stmt->stmt_id, param_no, length);
01293 
01294        conn = stmt->conn;
01295 
01296        SET_EMPTY_ERROR(stmt->error_info);
01297        SET_EMPTY_ERROR(stmt->conn->error_info);
01298 
01299        if (stmt->state < MYSQLND_STMT_PREPARED) {
01300               SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
01301               DBG_ERR("not prepared");
01302               DBG_RETURN(FAIL);
01303        }
01304        if (!stmt->param_bind) {
01305               SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
01306               DBG_ERR("command out of sync");
01307               DBG_RETURN(FAIL);
01308        }
01309 
01310        if (param_no >= stmt->param_count) {
01311               SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
01312               DBG_ERR("invalid param_no");
01313               DBG_RETURN(FAIL);
01314        }
01315        if (stmt->param_bind[param_no].type != MYSQL_TYPE_LONG_BLOB) {
01316               SET_STMT_ERROR(stmt, CR_INVALID_BUFFER_USE, UNKNOWN_SQLSTATE, mysqlnd_not_bound_as_blob);
01317               DBG_ERR("param_no is not of a blob type");
01318               DBG_RETURN(FAIL);
01319        }
01320 
01321        /*
01322          XXX: Unfortunately we have to allocate additional buffer to be able the
01323                      additional data, which is like a header inside the payload.
01324                      This should be optimised, but it will be a pervasive change, so
01325                      conn->m->simple_command() will accept not a buffer, but actually MYSQLND_STRING*
01326                      terminated by NULL, to send. If the strings are not big, we can collapse them
01327                      on the buffer every connection has, but otherwise we will just send them
01328                      one by one to the wire.
01329        */
01330 
01331        if (CONN_GET_STATE(conn) == CONN_READY) {
01332               size_t packet_len;
01333               cmd_buf = mnd_emalloc(packet_len = STMT_ID_LENGTH + 2 + length);
01334               if (cmd_buf) {
01335                      stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED;
01336 
01337                      int4store(cmd_buf, stmt->stmt_id);
01338                      int2store(cmd_buf + STMT_ID_LENGTH, param_no);
01339                      memcpy(cmd_buf + STMT_ID_LENGTH + 2, data, length);
01340 
01341                      /* COM_STMT_SEND_LONG_DATA doesn't send an OK packet*/
01342                      ret = conn->m->simple_command(conn, cmd, (char *)cmd_buf, packet_len, PROT_LAST , FALSE, TRUE TSRMLS_CC);
01343                      mnd_efree(cmd_buf);
01344                      if (FAIL == ret) {
01345                             stmt->error_info = conn->error_info;
01346                      }
01347               } else {
01348                      ret = FAIL;
01349                      SET_OOM_ERROR(stmt->error_info);
01350                      SET_OOM_ERROR(conn->error_info);
01351               }
01352               /*
01353                 Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not
01354                 sent response packets. According to documentation the only way to get an error
01355                 is to have out-of-memory on the server-side. However, that's not true, as if
01356                 max_allowed_packet_size is smaller than the chunk being sent to the server, the
01357                 latter will complain with an error message. However, normally we don't expect
01358                 an error message, thus we continue. When sending the next command, which expects
01359                 response we will read the unexpected data and error message will look weird.
01360                 Therefore we do non-blocking read to clean the line, if there is a need.
01361                 Nevertheless, there is a built-in protection when sending a command packet, that
01362                 checks if the line is clear - useful for debug purposes and to be switched off
01363                 in release builds.
01364 
01365                 Maybe we can make it automatic by checking what's the value of
01366                 max_allowed_packet_size on the server and resending the data.
01367               */
01368 #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
01369 #if HAVE_USLEEP && !defined(PHP_WIN32)
01370               usleep(120000);
01371 #endif
01372               if ((packet_len = conn->net->m.consume_uneaten_data(conn->net, cmd TSRMLS_CC))) {
01373                      php_error_docref(NULL TSRMLS_CC, E_WARNING, "There was an error "
01374                                                   "while sending long data. Probably max_allowed_packet_size "
01375                                                   "is smaller than the data. You have to increase it or send "
01376                                                   "smaller chunks of data. Answer was "MYSQLND_SZ_T_SPEC" bytes long.", packet_len);
01377                      SET_STMT_ERROR(stmt, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE,
01378                                                  "Server responded to COM_STMT_SEND_LONG_DATA.");
01379                      ret = FAIL;
01380               }
01381 #endif
01382        }
01383 
01384        DBG_INF(ret == PASS? "PASS":"FAIL");
01385        DBG_RETURN(ret);
01386 }
01387 /* }}} */
01388 
01389 
01390 /* {{{ mysqlnd_stmt::bind_parameters */
01391 static enum_func_status
01392 MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * const param_bind TSRMLS_DC)
01393 {
01394        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01395        DBG_ENTER("mysqlnd_stmt::bind_param");
01396        if (!stmt || !stmt->conn) {
01397               DBG_RETURN(FAIL);
01398        }
01399        DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
01400 
01401        if (stmt->state < MYSQLND_STMT_PREPARED) {
01402               SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
01403               DBG_ERR("not prepared");
01404               if (param_bind) {
01405                      s->m->free_parameter_bind(s, param_bind TSRMLS_CC);
01406               }
01407               DBG_RETURN(FAIL);
01408        }
01409 
01410        SET_EMPTY_ERROR(stmt->error_info);
01411        SET_EMPTY_ERROR(stmt->conn->error_info);
01412 
01413        if (stmt->param_count) {
01414               unsigned int i = 0;
01415 
01416               if (!param_bind) {
01417                      SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, "Re-binding (still) not supported");
01418                      DBG_ERR("Re-binding (still) not supported");
01419                      DBG_RETURN(FAIL);
01420               } else if (stmt->param_bind) {
01421                      DBG_INF("Binding");
01422                      /*
01423                        There is already result bound.
01424                        Forbid for now re-binding!!
01425                      */
01426                      for (i = 0; i < stmt->param_count; i++) {
01427                             /*
01428                               We may have the last reference, then call zval_ptr_dtor()
01429                               or we may leak memory.
01430                               Switching from bind_one_parameter to bind_parameters may result in zv being NULL
01431                             */
01432                             if (stmt->param_bind[i].zv) {
01433                                    zval_ptr_dtor(&stmt->param_bind[i].zv);
01434                             }
01435                      }
01436                      if (stmt->param_bind != param_bind) {
01437                             s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC);
01438                      }
01439               }
01440 
01441               stmt->param_bind = param_bind;
01442               for (i = 0; i < stmt->param_count; i++) {
01443                      /* The client will use stmt_send_long_data */
01444                      DBG_INF_FMT("%u is of type %u", i, stmt->param_bind[i].type);
01445                      /* Prevent from freeing */
01446                      /* Don't update is_ref, or we will leak during conversion */
01447                      Z_ADDREF_P(stmt->param_bind[i].zv);
01448                      stmt->param_bind[i].flags = 0;
01449                      if (stmt->param_bind[i].type == MYSQL_TYPE_LONG_BLOB) {
01450                             stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
01451                      }
01452               }
01453               stmt->send_types_to_server = 1;
01454        }
01455        DBG_INF("PASS");
01456        DBG_RETURN(PASS);
01457 }
01458 /* }}} */
01459 
01460 
01461 /* {{{ mysqlnd_stmt::bind_one_parameter */
01462 static enum_func_status
01463 MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter)(MYSQLND_STMT * const s, unsigned int param_no,
01464                                                                                      zval * const zv, zend_uchar type TSRMLS_DC)
01465 {
01466        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01467        DBG_ENTER("mysqlnd_stmt::bind_one_parameter");
01468        if (!stmt || !stmt->conn) {
01469               DBG_RETURN(FAIL);
01470        }
01471        DBG_INF_FMT("stmt=%lu param_no=%u param_count=%u type=%u",
01472                             stmt->stmt_id, param_no, stmt->param_count, type);
01473 
01474        if (stmt->state < MYSQLND_STMT_PREPARED) {
01475               SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
01476               DBG_ERR("not prepared");
01477               DBG_RETURN(FAIL);
01478        }
01479 
01480        if (param_no >= stmt->param_count) {
01481               SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
01482               DBG_ERR("invalid param_no");
01483               DBG_RETURN(FAIL);
01484        }
01485        SET_EMPTY_ERROR(stmt->error_info);
01486        SET_EMPTY_ERROR(stmt->conn->error_info);
01487 
01488        if (stmt->param_count) {
01489               if (!stmt->param_bind) {
01490                      stmt->param_bind = mnd_ecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND));
01491                      if (!stmt->param_bind) {
01492                             DBG_RETURN(FAIL);
01493                      }
01494               }
01495 
01496               /* Prevent from freeing */
01497               /* Don't update is_ref, or we will leak during conversion */
01498               Z_ADDREF_P(zv);
01499               DBG_INF("Binding");
01500               /* Release what we had, if we had */
01501               if (stmt->param_bind[param_no].zv) {
01502                      zval_ptr_dtor(&stmt->param_bind[param_no].zv);
01503               }
01504               if (type == MYSQL_TYPE_LONG_BLOB) {
01505                      /* The client will use stmt_send_long_data */
01506                      stmt->param_bind[param_no].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;
01507               }
01508               stmt->param_bind[param_no].zv = zv;
01509               stmt->param_bind[param_no].type = type;
01510 
01511               stmt->send_types_to_server = 1;
01512        }
01513        DBG_INF("PASS");
01514        DBG_RETURN(PASS);
01515 }
01516 /* }}} */
01517 
01518 
01519 /* {{{ mysqlnd_stmt::refresh_bind_param */
01520 static enum_func_status
01521 MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param)(MYSQLND_STMT * const s TSRMLS_DC)
01522 {
01523        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01524        DBG_ENTER("mysqlnd_stmt::refresh_bind_param");
01525        if (!stmt || !stmt->conn) {
01526               DBG_RETURN(FAIL);
01527        }
01528        DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count);
01529 
01530        if (stmt->state < MYSQLND_STMT_PREPARED) {
01531               SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
01532               DBG_ERR("not prepared");
01533               DBG_RETURN(FAIL);
01534        }
01535 
01536        SET_EMPTY_ERROR(stmt->error_info);
01537        SET_EMPTY_ERROR(stmt->conn->error_info);
01538 
01539        if (stmt->param_count) {
01540               stmt->send_types_to_server = 1;
01541        }
01542        DBG_INF("PASS");
01543        DBG_RETURN(PASS);
01544 }
01545 /* }}} */
01546 
01547 
01548 /* {{{ mysqlnd_stmt::bind_result */
01549 static enum_func_status
01550 MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const s,
01551                                                                         MYSQLND_RESULT_BIND * const result_bind TSRMLS_DC)
01552 {
01553        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01554        DBG_ENTER("mysqlnd_stmt::bind_result");
01555        if (!stmt || !stmt->conn) {
01556               DBG_RETURN(FAIL);
01557        }
01558        DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
01559 
01560        if (stmt->state < MYSQLND_STMT_PREPARED) {
01561               SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
01562               if (result_bind) {
01563                      s->m->free_result_bind(s, result_bind TSRMLS_CC);
01564               }
01565               DBG_ERR("not prepared");
01566               DBG_RETURN(FAIL);
01567        }
01568 
01569        SET_EMPTY_ERROR(stmt->error_info);
01570        SET_EMPTY_ERROR(stmt->conn->error_info);
01571 
01572        if (stmt->field_count) {
01573               unsigned int i = 0;
01574 
01575               if (!result_bind) {
01576                      DBG_ERR("no result bind passed");
01577                      DBG_RETURN(FAIL);
01578               }
01579 
01580               mysqlnd_stmt_separate_result_bind(s TSRMLS_CC);
01581               stmt->result_zvals_separated_once = FALSE;
01582               stmt->result_bind = result_bind;
01583               for (i = 0; i < stmt->field_count; i++) {
01584                      /* Prevent from freeing */
01585                      Z_ADDREF_P(stmt->result_bind[i].zv);
01586                      DBG_INF_FMT("ref of %p = %u", stmt->result_bind[i].zv, Z_REFCOUNT_P(stmt->result_bind[i].zv));
01587                      /*
01588                        Don't update is_ref !!! it's not our job
01589                        Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
01590                        will fail.
01591                      */
01592                      stmt->result_bind[i].bound = TRUE;
01593               }
01594        } else if (result_bind) {
01595               s->m->free_result_bind(s, result_bind TSRMLS_CC);
01596        }
01597        DBG_INF("PASS");
01598        DBG_RETURN(PASS);
01599 }
01600 /* }}} */
01601 
01602 
01603 /* {{{ mysqlnd_stmt::bind_result */
01604 static enum_func_status
01605 MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned int param_no TSRMLS_DC)
01606 {
01607        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01608        DBG_ENTER("mysqlnd_stmt::bind_result");
01609        if (!stmt || !stmt->conn) {
01610               DBG_RETURN(FAIL);
01611        }
01612        DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count);
01613 
01614        if (stmt->state < MYSQLND_STMT_PREPARED) {
01615               SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared);
01616               DBG_ERR("not prepared");
01617               DBG_RETURN(FAIL);
01618        }
01619 
01620        if (param_no >= stmt->field_count) {
01621               SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number");
01622               DBG_ERR("invalid param_no");
01623               DBG_RETURN(FAIL);
01624        }
01625 
01626        SET_EMPTY_ERROR(stmt->error_info);
01627        SET_EMPTY_ERROR(stmt->conn->error_info);
01628 
01629        if (stmt->field_count) {
01630               mysqlnd_stmt_separate_one_result_bind(s, param_no TSRMLS_CC);
01631               /* Guaranteed is that stmt->result_bind is NULL */
01632               if (!stmt->result_bind) {
01633                      stmt->result_bind = mnd_ecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND));
01634               } else {
01635                      stmt->result_bind = mnd_erealloc(stmt->result_bind, stmt->field_count * sizeof(MYSQLND_RESULT_BIND));
01636               }
01637               if (!stmt->result_bind) {
01638                      DBG_RETURN(FAIL);
01639               }
01640               ALLOC_INIT_ZVAL(stmt->result_bind[param_no].zv);
01641               /*
01642                 Don't update is_ref !!! it's not our job
01643                 Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt
01644                 will fail.
01645               */
01646               stmt->result_bind[param_no].bound = TRUE;
01647        }
01648        DBG_INF("PASS");
01649        DBG_RETURN(PASS);
01650 }
01651 /* }}} */
01652 
01653 
01654 /* {{{ mysqlnd_stmt::insert_id */
01655 static uint64_t
01656 MYSQLND_METHOD(mysqlnd_stmt, insert_id)(const MYSQLND_STMT * const s TSRMLS_DC)
01657 {
01658        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01659        return stmt? stmt->upsert_status.last_insert_id : 0;
01660 }
01661 /* }}} */
01662 
01663 
01664 /* {{{ mysqlnd_stmt::affected_rows */
01665 static uint64_t
01666 MYSQLND_METHOD(mysqlnd_stmt, affected_rows)(const MYSQLND_STMT * const s TSRMLS_DC)
01667 {
01668        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01669        return stmt? stmt->upsert_status.affected_rows : 0;
01670 }
01671 /* }}} */
01672 
01673 
01674 /* {{{ mysqlnd_stmt::num_rows */
01675 static uint64_t
01676 MYSQLND_METHOD(mysqlnd_stmt, num_rows)(const MYSQLND_STMT * const s TSRMLS_DC)
01677 {
01678        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01679        return stmt && stmt->result? mysqlnd_num_rows(stmt->result):0;
01680 }
01681 /* }}} */
01682 
01683 
01684 /* {{{ mysqlnd_stmt::warning_count */
01685 static unsigned int
01686 MYSQLND_METHOD(mysqlnd_stmt, warning_count)(const MYSQLND_STMT * const s TSRMLS_DC)
01687 {
01688        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01689        return stmt? stmt->upsert_status.warning_count : 0;
01690 }
01691 /* }}} */
01692 
01693 
01694 /* {{{ mysqlnd_stmt::server_status */
01695 static unsigned int
01696 MYSQLND_METHOD(mysqlnd_stmt, server_status)(const MYSQLND_STMT * const s TSRMLS_DC)
01697 {
01698        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01699        return stmt? stmt->upsert_status.server_status : 0;
01700 }
01701 /* }}} */
01702 
01703 
01704 /* {{{ mysqlnd_stmt::field_count */
01705 static unsigned int
01706 MYSQLND_METHOD(mysqlnd_stmt, field_count)(const MYSQLND_STMT * const s TSRMLS_DC)
01707 {
01708        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01709        return stmt? stmt->field_count : 0;
01710 }
01711 /* }}} */
01712 
01713 
01714 /* {{{ mysqlnd_stmt::param_count */
01715 static unsigned int
01716 MYSQLND_METHOD(mysqlnd_stmt, param_count)(const MYSQLND_STMT * const s TSRMLS_DC)
01717 {
01718        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01719        return stmt? stmt->param_count : 0;
01720 }
01721 /* }}} */
01722 
01723 
01724 /* {{{ mysqlnd_stmt::errno */
01725 static unsigned int
01726 MYSQLND_METHOD(mysqlnd_stmt, errno)(const MYSQLND_STMT * const s TSRMLS_DC)
01727 {
01728        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01729        return stmt? stmt->error_info.error_no : 0;
01730 }
01731 /* }}} */
01732 
01733 
01734 /* {{{ mysqlnd_stmt::error */
01735 static const char *
01736 MYSQLND_METHOD(mysqlnd_stmt, error)(const MYSQLND_STMT * const s TSRMLS_DC)
01737 {
01738        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01739        return stmt? stmt->error_info.error : 0;
01740 }
01741 /* }}} */
01742 
01743 
01744 /* {{{ mysqlnd_stmt::sqlstate */
01745 static const char *
01746 MYSQLND_METHOD(mysqlnd_stmt, sqlstate)(const MYSQLND_STMT * const s TSRMLS_DC)
01747 {
01748        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01749        return stmt && stmt->error_info.sqlstate[0] ? stmt->error_info.sqlstate:MYSQLND_SQLSTATE_NULL;
01750 }
01751 /* }}} */
01752 
01753 
01754 /* {{{ mysqlnd_stmt::data_seek */
01755 static enum_func_status
01756 MYSQLND_METHOD(mysqlnd_stmt, data_seek)(const MYSQLND_STMT * const s, uint64_t row TSRMLS_DC)
01757 {
01758        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01759        return stmt && stmt->result? stmt->result->m.seek_data(stmt->result, row TSRMLS_CC) : FAIL;
01760 }
01761 /* }}} */
01762 
01763 
01764 /* {{{ mysqlnd_stmt::param_metadata */
01765 static MYSQLND_RES *
01766 MYSQLND_METHOD(mysqlnd_stmt, param_metadata)(MYSQLND_STMT * const s TSRMLS_DC)
01767 {
01768        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01769        if (!stmt || !stmt->param_count) {
01770               return NULL;
01771        }
01772        return NULL;
01773 }
01774 /* }}} */
01775 
01776 
01777 /* {{{ mysqlnd_stmt::result_metadata */
01778 static MYSQLND_RES *
01779 MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s TSRMLS_DC)
01780 {
01781        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01782        MYSQLND_RES *result;
01783 
01784        DBG_ENTER("mysqlnd_stmt::result_metadata");
01785        if (!stmt) {
01786               DBG_RETURN(NULL);
01787        }
01788        DBG_INF_FMT("stmt=%u field_count=%u", stmt->stmt_id, stmt->field_count);
01789 
01790        if (!stmt->field_count || !stmt->conn || !stmt->result || !stmt->result->meta) {
01791               DBG_INF("NULL");
01792               DBG_RETURN(NULL);
01793        }
01794 
01795        if (stmt->update_max_length && stmt->result->stored_data) {
01796               /* stored result, we have to update the max_length before we clone the meta data :( */
01797               stmt->result->m.initialize_result_set_rest(stmt->result TSRMLS_CC);
01798        }
01799        /*
01800          TODO: This implementation is kind of a hack,
01801                      find a better way to do it. In different functions I have put
01802                      fuses to check for result->m.fetch_row() being NULL. This should
01803                      be handled in a better way.
01804 
01805          In the meantime we don't need a zval cache reference for this fake
01806          result set, so we don't get one.
01807        */
01808        do {
01809               result = stmt->conn->m->result_init(stmt->field_count, stmt->persistent TSRMLS_CC);
01810               if (!result) {
01811                      break;
01812               }
01813               result->type = MYSQLND_RES_NORMAL;
01814               result->m.fetch_row = result->m.fetch_row_normal_unbuffered;
01815               result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED));
01816               if (!result->unbuf) {
01817                      break;
01818               }
01819               result->unbuf->eof_reached = TRUE;
01820               result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC);
01821               if (!result->meta) {
01822                      break;
01823               }
01824 
01825               DBG_INF_FMT("result=%p", result);
01826               DBG_RETURN(result);
01827        } while (0);
01828 
01829        SET_OOM_ERROR(stmt->conn->error_info);
01830        if (result) {
01831               result->m.free_result(result, TRUE TSRMLS_CC);
01832        }
01833        DBG_RETURN(NULL);
01834 }
01835 /* }}} */
01836 
01837 
01838 /* {{{ mysqlnd_stmt::attr_set */
01839 static enum_func_status
01840 MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const s,
01841                                                                   enum mysqlnd_stmt_attr attr_type,
01842                                                                   const void * const value TSRMLS_DC)
01843 {
01844        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01845        DBG_ENTER("mysqlnd_stmt::attr_set");
01846        if (!stmt) {
01847               DBG_RETURN(FAIL);
01848        }
01849        DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type);
01850 
01851        switch (attr_type) {
01852               case STMT_ATTR_UPDATE_MAX_LENGTH:{
01853                      zend_uchar bval = *(zend_uchar *) value;
01854                      /*
01855                        XXX : libmysql uses my_bool, but mysqli uses ulong as storage on the stack
01856                        and mysqlnd won't be used out of the scope of PHP -> use ulong.
01857                      */
01858                      stmt->update_max_length = bval? TRUE:FALSE;
01859                      break;
01860               }
01861               case STMT_ATTR_CURSOR_TYPE: {
01862                      unsigned int ival = *(unsigned int *) value;
01863                      if (ival > (unsigned long) CURSOR_TYPE_READ_ONLY) {
01864                             SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
01865                             DBG_INF("FAIL");
01866                             DBG_RETURN(FAIL);
01867                      }
01868                      stmt->flags = ival;
01869                      break;
01870               }
01871               case STMT_ATTR_PREFETCH_ROWS: {
01872                      unsigned int ival = *(unsigned int *) value;
01873                      if (ival == 0) {
01874                             ival = MYSQLND_DEFAULT_PREFETCH_ROWS;
01875                      } else if (ival > 1) {
01876                             SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
01877                             DBG_INF("FAIL");
01878                             DBG_RETURN(FAIL);
01879                      }
01880                      stmt->prefetch_rows = ival;
01881                      break;
01882               }
01883               default:
01884                      SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented");
01885                      DBG_RETURN(FAIL);
01886        }
01887        DBG_INF("PASS");
01888        DBG_RETURN(PASS);
01889 }
01890 /* }}} */
01891 
01892 
01893 /* {{{ mysqlnd_stmt::attr_get */
01894 static enum_func_status
01895 MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s,
01896                                                                   enum mysqlnd_stmt_attr attr_type,
01897                                                                   void * const value TSRMLS_DC)
01898 {
01899        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01900        DBG_ENTER("mysqlnd_stmt::attr_set");
01901        if (!stmt) {
01902               DBG_RETURN(FAIL);
01903        }
01904        DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type);
01905 
01906        switch (attr_type) {
01907               case STMT_ATTR_UPDATE_MAX_LENGTH:
01908                      *(zend_bool *) value= stmt->update_max_length;
01909                      break;
01910               case STMT_ATTR_CURSOR_TYPE:
01911                      *(unsigned long *) value= stmt->flags;
01912                      break;
01913               case STMT_ATTR_PREFETCH_ROWS:
01914                      *(unsigned long *) value= stmt->prefetch_rows;
01915                      break;
01916               default:
01917                      DBG_RETURN(FAIL);
01918        }
01919        DBG_INF_FMT("value=%lu", value);
01920        DBG_RETURN(PASS);
01921 }
01922 /* }}} */
01923 
01924 /* free_result() doesn't actually free stmt->result but only the buffers */
01925 /* {{{ mysqlnd_stmt::free_result */
01926 static enum_func_status
01927 MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const s TSRMLS_DC)
01928 {
01929        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01930        DBG_ENTER("mysqlnd_stmt::free_result");
01931        if (!stmt || !stmt->conn) {
01932               DBG_RETURN(FAIL);
01933        }
01934        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
01935 
01936        if (!stmt->result) {
01937               DBG_INF("no result");
01938               DBG_RETURN(PASS);
01939        }
01940 
01941        /*
01942          If right after execute() we have to call the appropriate
01943          use_result() or store_result() and clean.
01944        */
01945        if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
01946               DBG_INF("fetching result set header");
01947               /* Do implicit use_result and then flush the result */
01948               stmt->default_rset_handler = s->m->use_result;
01949               stmt->default_rset_handler(s TSRMLS_CC);
01950        }
01951 
01952        if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) {
01953               DBG_INF("skipping result");
01954               /* Flush if anything is left and unbuffered set */
01955               stmt->result->m.skip_result(stmt->result TSRMLS_CC);
01956               /*
01957                 Separate the bound variables, which point to the result set, then
01958                 destroy the set.
01959               */
01960               mysqlnd_stmt_separate_result_bind(s TSRMLS_CC);
01961 
01962               /* Now we can destroy the result set */
01963               stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC);
01964        }
01965 
01966        if (stmt->state > MYSQLND_STMT_PREPARED) {
01967               /* As the buffers have been freed, we should go back to PREPARED */
01968               stmt->state = MYSQLND_STMT_PREPARED;
01969        }
01970 
01971        /* Line is free! */
01972        CONN_SET_STATE(stmt->conn, CONN_READY);
01973 
01974        DBG_RETURN(PASS);
01975 }
01976 /* }}} */
01977 
01978 
01979 /* {{{ mysqlnd_stmt_separate_result_bind */
01980 static void
01981 mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s TSRMLS_DC)
01982 {
01983        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
01984        unsigned int i;
01985 
01986        DBG_ENTER("mysqlnd_stmt_separate_result_bind");
01987        if (!stmt) {
01988               DBG_VOID_RETURN;
01989        }
01990        DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u",
01991                             stmt->stmt_id, stmt->result_bind, stmt->field_count);
01992 
01993        if (!stmt->result_bind) {
01994               DBG_VOID_RETURN;
01995        }
01996 
01997        /*
01998          Because only the bound variables can point to our internal buffers, then
01999          separate or free only them. Free is possible because the user could have
02000          lost reference.
02001        */
02002        for (i = 0; i < stmt->field_count; i++) {
02003               /* Let's try with no cache */
02004               if (stmt->result_bind[i].bound == TRUE) {
02005                      DBG_INF_FMT("%u has refcount=%u", i, Z_REFCOUNT_P(stmt->result_bind[i].zv));
02006                      /*
02007                        We have to separate the actual zval value of the bound
02008                        variable from our allocated zvals or we will face double-free
02009                      */
02010                      if (Z_REFCOUNT_P(stmt->result_bind[i].zv) > 1) {
02011 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
02012                             zval_copy_ctor(stmt->result_bind[i].zv);
02013 #endif
02014                             zval_ptr_dtor(&stmt->result_bind[i].zv);
02015                      } else {
02016                             /*
02017                               If it is a string, what is pointed will be freed
02018                               later in free_result(). We need to remove the variable to
02019                               which the user has lost reference.
02020                             */
02021 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
02022                             ZVAL_NULL(stmt->result_bind[i].zv);
02023 #endif
02024                             zval_ptr_dtor(&stmt->result_bind[i].zv);
02025                      }
02026               }
02027        }
02028        s->m->free_result_bind(s, stmt->result_bind TSRMLS_CC);
02029        stmt->result_bind = NULL;
02030 
02031        DBG_VOID_RETURN;
02032 }
02033 /* }}} */
02034 
02035 
02036 /* {{{ mysqlnd_stmt_separate_one_result_bind */
02037 static void
02038 mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s, unsigned int param_no TSRMLS_DC)
02039 {
02040        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
02041        DBG_ENTER("mysqlnd_stmt_separate_one_result_bind");
02042        if (!stmt) {
02043               DBG_VOID_RETURN;
02044        }
02045        DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u param_no=%u",
02046                             stmt->stmt_id, stmt->result_bind, stmt->field_count, param_no);
02047 
02048        if (!stmt->result_bind) {
02049               DBG_VOID_RETURN;
02050        }
02051 
02052        /*
02053          Because only the bound variables can point to our internal buffers, then
02054          separate or free only them. Free is possible because the user could have
02055          lost reference.
02056        */
02057        /* Let's try with no cache */
02058        if (stmt->result_bind[param_no].bound == TRUE) {
02059               DBG_INF_FMT("%u has refcount=%u", param_no, Z_REFCOUNT_P(stmt->result_bind[param_no].zv));
02060               /*
02061                 We have to separate the actual zval value of the bound
02062                 variable from our allocated zvals or we will face double-free
02063               */
02064               if (Z_REFCOUNT_P(stmt->result_bind[param_no].zv) > 1) {
02065 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
02066                      zval_copy_ctor(stmt->result_bind[param_no].zv);
02067 #endif
02068                      zval_ptr_dtor(&stmt->result_bind[param_no].zv);
02069               } else {
02070                      /*
02071                        If it is a string, what is pointed will be freed
02072                        later in free_result(). We need to remove the variable to
02073                        which the user has lost reference.
02074                      */
02075 #ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF
02076                      ZVAL_NULL(stmt->result_bind[param_no].zv);
02077 #endif
02078                      zval_ptr_dtor(&stmt->result_bind[param_no].zv);
02079               }
02080        }
02081 
02082        DBG_VOID_RETURN;
02083 }
02084 /* }}} */
02085 
02086 
02087 /* {{{ mysqlnd_stmt::free_stmt_content */
02088 static void
02089 MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content)(MYSQLND_STMT * const s TSRMLS_DC)
02090 {
02091        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
02092        DBG_ENTER("mysqlnd_stmt::free_stmt_content");
02093        if (!stmt) {
02094               DBG_VOID_RETURN;
02095        }
02096        DBG_INF_FMT("stmt=%lu param_bind=%p param_count=%u",
02097                             stmt->stmt_id, stmt->param_bind, stmt->param_count);
02098 
02099        /* Destroy the input bind */
02100        if (stmt->param_bind) {
02101               unsigned int i;
02102               /*
02103                 Because only the bound variables can point to our internal buffers, then
02104                 separate or free only them. Free is possible because the user could have
02105                 lost reference.
02106               */
02107               for (i = 0; i < stmt->param_count; i++) {
02108                      /*
02109                        If bind_one_parameter was used, but not everything was
02110                        bound and nothing was fetched, then some `zv` could be NULL
02111                      */
02112                      if (stmt->param_bind[i].zv) {
02113                             zval_ptr_dtor(&stmt->param_bind[i].zv);
02114                      }
02115               }
02116               s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC);
02117               stmt->param_bind = NULL;
02118        }
02119 
02120        /*
02121          First separate the bound variables, which point to the result set, then
02122          destroy the set.
02123        */
02124        mysqlnd_stmt_separate_result_bind(s TSRMLS_CC);
02125        /* Not every statement has a result set attached */
02126        if (stmt->result) {
02127               stmt->result->m.free_result_internal(stmt->result TSRMLS_CC);
02128               stmt->result = NULL;
02129        }
02130 
02131        DBG_VOID_RETURN;
02132 }
02133 /* }}} */
02134 
02135 
02136 /* {{{ mysqlnd_stmt::net_close */
02137 static enum_func_status
02138 MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close)(MYSQLND_STMT * const s, zend_bool implicit TSRMLS_DC)
02139 {
02140        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
02141        MYSQLND * conn;
02142        zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */];
02143        enum_mysqlnd_collected_stats statistic = STAT_LAST;
02144 
02145        DBG_ENTER("mysqlnd_stmt::net_close");
02146        if (!stmt || !stmt->conn) {
02147               DBG_RETURN(FAIL);
02148        }
02149        DBG_INF_FMT("stmt=%lu", stmt->stmt_id);
02150 
02151        conn = stmt->conn;
02152 
02153        SET_EMPTY_ERROR(stmt->error_info);
02154        SET_EMPTY_ERROR(stmt->conn->error_info);
02155 
02156        /*
02157          If the user decided to close the statement right after execute()
02158          We have to call the appropriate use_result() or store_result() and
02159          clean.
02160        */
02161        do {
02162               DBG_INF_FMT("stmt->state=%u", stmt->state);
02163               if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
02164                      DBG_INF("fetching result set header");
02165                      stmt->default_rset_handler(s TSRMLS_CC);
02166                      stmt->state = MYSQLND_STMT_USER_FETCHING;
02167               }
02168 
02169               /* unbuffered set not fetched to the end ? Clean the line */
02170               if (stmt->result) {
02171                      DBG_INF("skipping result");
02172                      stmt->result->m.skip_result(stmt->result TSRMLS_CC);
02173               }
02174        } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS);
02175        /*
02176          After this point we are allowed to free the result set,
02177          as we have cleaned the line
02178        */
02179        if (stmt->stmt_id) {
02180               MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE?   STAT_FREE_RESULT_IMPLICIT:
02181                                                                                                   STAT_FREE_RESULT_EXPLICIT);
02182 
02183               int4store(cmd_buf, stmt->stmt_id);
02184               if (CONN_GET_STATE(conn) == CONN_READY &&
02185                      FAIL == conn->m->simple_command(conn, COM_STMT_CLOSE, (char *)cmd_buf, sizeof(cmd_buf),
02186                                                                          PROT_LAST /* COM_STMT_CLOSE doesn't send an OK packet*/,
02187                                                                          FALSE, TRUE TSRMLS_CC)) {
02188                      stmt->error_info = conn->error_info;
02189                      DBG_RETURN(FAIL);
02190               }
02191        }
02192        switch (stmt->execute_count) {
02193               case 0:
02194                      statistic = STAT_PS_PREPARED_NEVER_EXECUTED;
02195                      break;
02196               case 1:
02197                      statistic = STAT_PS_PREPARED_ONCE_USED;
02198                      break;
02199               default:
02200                      break;
02201        }
02202        if (statistic != STAT_LAST) {
02203               MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
02204        }
02205 
02206        if (stmt->execute_cmd_buffer.buffer) {
02207               mnd_pefree(stmt->execute_cmd_buffer.buffer, stmt->persistent);
02208               stmt->execute_cmd_buffer.buffer = NULL;
02209        }
02210 
02211        s->m->free_stmt_content(s TSRMLS_CC);
02212 
02213        if (stmt->conn) {
02214               stmt->conn->m->free_reference(stmt->conn TSRMLS_CC);
02215               stmt->conn = NULL;
02216        }
02217 
02218        DBG_RETURN(PASS);
02219 }
02220 /* }}} */
02221 
02222 /* {{{ mysqlnd_stmt::dtor */
02223 static enum_func_status
02224 MYSQLND_METHOD(mysqlnd_stmt, dtor)(MYSQLND_STMT * const s, zend_bool implicit TSRMLS_DC)
02225 {
02226        MYSQLND_STMT_DATA * stmt = (s != NULL) ? s->data:NULL;
02227        enum_func_status ret = FAIL;
02228        zend_bool persistent = (s != NULL) ? s->persistent : 0;
02229 
02230        DBG_ENTER("mysqlnd_stmt::dtor");
02231        if (stmt) {
02232 
02233               DBG_INF_FMT("stmt=%p", stmt);
02234 
02235               MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE?   STAT_STMT_CLOSE_IMPLICIT:
02236                                                                                                   STAT_STMT_CLOSE_EXPLICIT);
02237 
02238               ret = s->m->net_close(s, implicit TSRMLS_CC);
02239               mnd_pefree(stmt, persistent);
02240        }
02241        mnd_pefree(s, persistent);
02242 
02243        DBG_INF(ret == PASS? "PASS":"FAIL");
02244        DBG_RETURN(ret);
02245 }
02246 /* }}} */
02247 
02248 
02249 /* {{{ mysqlnd_stmt::alloc_param_bind */
02250 static MYSQLND_PARAM_BIND *
02251 MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind)(MYSQLND_STMT * const s TSRMLS_DC)
02252 {
02253        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
02254        DBG_ENTER("mysqlnd_stmt::alloc_param_bind");
02255        if (!stmt) {
02256               DBG_RETURN(NULL);
02257        }
02258        DBG_RETURN(mnd_pecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND), stmt->persistent));
02259 }
02260 /* }}} */
02261 
02262 
02263 /* {{{ mysqlnd_stmt::alloc_result_bind */
02264 static MYSQLND_RESULT_BIND *
02265 MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind)(MYSQLND_STMT * const s TSRMLS_DC)
02266 {
02267        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
02268        DBG_ENTER("mysqlnd_stmt::alloc_result_bind");
02269        if (!stmt) {
02270               DBG_RETURN(NULL);
02271        }
02272        DBG_RETURN(mnd_pecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND), stmt->persistent));
02273 }
02274 /* }}} */
02275 
02276 
02277 /* {{{ param_bind::free_parameter_bind */
02278 PHPAPI void
02279 MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * param_bind TSRMLS_DC)
02280 {
02281        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
02282        if (stmt) {
02283               mnd_pefree(param_bind, stmt->persistent);
02284        }
02285 }
02286 /* }}} */
02287 
02288 
02289 /* {{{ mysqlnd_stmt::free_result_bind */
02290 PHPAPI void
02291 MYSQLND_METHOD(mysqlnd_stmt, free_result_bind)(MYSQLND_STMT * const s, MYSQLND_RESULT_BIND * result_bind TSRMLS_DC)
02292 {
02293        MYSQLND_STMT_DATA * stmt = s? s->data:NULL;
02294        if (stmt) {
02295               mnd_pefree(result_bind, stmt->persistent);
02296        }
02297 }
02298 /* }}} */
02299 
02300 
02301 
02302 MYSQLND_CLASS_METHODS_START(mysqlnd_stmt)
02303        MYSQLND_METHOD(mysqlnd_stmt, prepare),
02304        MYSQLND_METHOD(mysqlnd_stmt, execute),
02305        MYSQLND_METHOD(mysqlnd_stmt, use_result),
02306        MYSQLND_METHOD(mysqlnd_stmt, store_result),
02307        MYSQLND_METHOD(mysqlnd_stmt, get_result),
02308        MYSQLND_METHOD(mysqlnd_stmt, more_results),
02309        MYSQLND_METHOD(mysqlnd_stmt, next_result),
02310        MYSQLND_METHOD(mysqlnd_stmt, free_result),
02311        MYSQLND_METHOD(mysqlnd_stmt, data_seek),
02312        MYSQLND_METHOD(mysqlnd_stmt, reset),
02313        MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close),
02314        MYSQLND_METHOD(mysqlnd_stmt, dtor),
02315 
02316        MYSQLND_METHOD(mysqlnd_stmt, fetch),
02317 
02318        MYSQLND_METHOD(mysqlnd_stmt, bind_parameters),
02319        MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter),
02320        MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param),
02321        MYSQLND_METHOD(mysqlnd_stmt, bind_result),
02322        MYSQLND_METHOD(mysqlnd_stmt, bind_one_result),
02323        MYSQLND_METHOD(mysqlnd_stmt, send_long_data),
02324        MYSQLND_METHOD(mysqlnd_stmt, param_metadata),
02325        MYSQLND_METHOD(mysqlnd_stmt, result_metadata),
02326 
02327        MYSQLND_METHOD(mysqlnd_stmt, insert_id),
02328        MYSQLND_METHOD(mysqlnd_stmt, affected_rows),
02329        MYSQLND_METHOD(mysqlnd_stmt, num_rows),
02330 
02331        MYSQLND_METHOD(mysqlnd_stmt, param_count),
02332        MYSQLND_METHOD(mysqlnd_stmt, field_count),
02333        MYSQLND_METHOD(mysqlnd_stmt, warning_count),
02334 
02335        MYSQLND_METHOD(mysqlnd_stmt, errno),
02336        MYSQLND_METHOD(mysqlnd_stmt, error),
02337        MYSQLND_METHOD(mysqlnd_stmt, sqlstate),
02338 
02339        MYSQLND_METHOD(mysqlnd_stmt, attr_get),
02340        MYSQLND_METHOD(mysqlnd_stmt, attr_set),
02341 
02342 
02343        MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind),
02344        MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind),
02345        MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind),
02346        MYSQLND_METHOD(mysqlnd_stmt, free_result_bind),
02347        MYSQLND_METHOD(mysqlnd_stmt, server_status),
02348        mysqlnd_stmt_execute_generate_request,
02349        mysqlnd_stmt_execute_parse_response,
02350        MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content)
02351 MYSQLND_CLASS_METHODS_END;
02352 
02353 
02354 /* {{{ _mysqlnd_stmt_init */
02355 MYSQLND_STMT * _mysqlnd_stmt_init(MYSQLND * const conn TSRMLS_DC)
02356 {
02357        size_t alloc_size = sizeof(MYSQLND_STMT) + mysqlnd_plugin_count() * sizeof(void *);
02358        MYSQLND_STMT * ret = mnd_pecalloc(1, alloc_size, conn->persistent);
02359        MYSQLND_STMT_DATA * stmt = NULL;
02360 
02361        DBG_ENTER("_mysqlnd_stmt_init");
02362        do {
02363               if (!ret) {
02364                      break;
02365               }
02366               ret->m = mysqlnd_stmt_methods;
02367               ret->persistent = conn->persistent;
02368 
02369               stmt = ret->data = mnd_pecalloc(1, sizeof(MYSQLND_STMT_DATA), conn->persistent);
02370               DBG_INF_FMT("stmt=%p", stmt);
02371               if (!stmt) {
02372                      break;
02373               }
02374               stmt->persistent = conn->persistent;
02375               stmt->state = MYSQLND_STMT_INITTED;
02376               stmt->execute_cmd_buffer.length = 4096;
02377               stmt->execute_cmd_buffer.buffer = mnd_pemalloc(stmt->execute_cmd_buffer.length, stmt->persistent);
02378               if (!stmt->execute_cmd_buffer.buffer) {
02379                      break;
02380               }
02381 
02382               stmt->prefetch_rows = MYSQLND_DEFAULT_PREFETCH_ROWS;
02383               /*
02384                 Mark that we reference the connection, thus it won't be
02385                 be destructed till there is open statements. The last statement
02386                 or normal query result will close it then.
02387               */
02388               stmt->conn = conn->m->get_reference(conn TSRMLS_CC);
02389 
02390               DBG_RETURN(ret);
02391        } while (0);
02392 
02393        SET_OOM_ERROR(conn->error_info);
02394        if (ret) {
02395               ret->m->dtor(ret, TRUE TSRMLS_CC);
02396               ret = NULL;
02397        }
02398        DBG_RETURN(NULL);
02399 }
02400 /* }}} */
02401 
02402 
02403 /* {{{ _mysqlnd_plugin_get_plugin_stmt_data */
02404 PHPAPI void ** _mysqlnd_plugin_get_plugin_stmt_data(const MYSQLND_STMT * stmt, unsigned int plugin_id TSRMLS_DC)
02405 {
02406        DBG_ENTER("_mysqlnd_plugin_get_plugin_stmt_data");
02407        DBG_INF_FMT("plugin_id=%u", plugin_id);
02408        if (!stmt || plugin_id >= mysqlnd_plugin_count()) {
02409               return NULL;
02410        }
02411        DBG_RETURN((void *)((char *)stmt + sizeof(MYSQLND_STMT) + plugin_id * sizeof(void *)));
02412 }
02413 /* }}} */
02414 
02415 
02416 /* {{{ _mysqlnd_init_ps_subsystem */
02417 void _mysqlnd_init_ps_subsystem()
02418 {
02419        mysqlnd_stmt_methods = &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_stmt);
02420        _mysqlnd_init_ps_fetch_subsystem();
02421 }
02422 /* }}} */
02423 
02424 
02425 /* {{{ mysqlnd_conn_get_methods */
02426 PHPAPI struct st_mysqlnd_stmt_methods * mysqlnd_stmt_get_methods()
02427 {
02428        return mysqlnd_stmt_methods;
02429 }
02430 /* }}} */
02431 
02432 
02433 /* {{{ mysqlnd_conn_set_methods */
02434 PHPAPI void mysqlnd_stmt_set_methods(struct st_mysqlnd_stmt_methods *methods)
02435 {
02436        mysqlnd_stmt_methods = methods;
02437 }
02438 /* }}} */
02439 
02440 
02441 /*
02442  * Local variables:
02443  * tab-width: 4
02444  * c-basic-offset: 4
02445  * End:
02446  * vim600: noet sw=4 ts=4 fdm=marker
02447  * vim<600: noet sw=4 ts=4
02448  */