diff options
Diffstat (limited to 'ext/mysqlnd/mysqlnd_ps.c')
-rw-r--r-- | ext/mysqlnd/mysqlnd_ps.c | 427 |
1 files changed, 226 insertions, 201 deletions
diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c index 15b293825b..1b2602e6b6 100644 --- a/ext/mysqlnd/mysqlnd_ps.c +++ b/ext/mysqlnd/mysqlnd_ps.c @@ -1,8 +1,8 @@ /* +----------------------------------------------------------------------+ - | PHP Version 5 | + | PHP Version 7 | +----------------------------------------------------------------------+ - | Copyright (c) 2006-2013 The PHP Group | + | Copyright (c) 2006-2014 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | @@ -38,19 +38,11 @@ const char * const mysqlnd_stmt_not_prepared = "Statement not prepared"; /* Exported by mysqlnd_ps_codec.c */ 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); - -enum_func_status mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param, - unsigned int flags, - zend_bool *fetched_anything TSRMLS_DC); - -enum_func_status mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, - unsigned int flags, - zend_bool *fetched_anything TSRMLS_DC); +enum_func_status mysqlnd_stmt_execute_batch_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC); static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC); static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC); - /* {{{ mysqlnd_stmt::store_result */ static MYSQLND_RES * MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s TSRMLS_DC) @@ -95,15 +87,42 @@ MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s TSRMLS_DC) result = stmt->result; result->type = MYSQLND_RES_PS_BUF; - result->m.fetch_row = mysqlnd_stmt_fetch_row_buffered; - result->m.fetch_lengths = NULL;/* makes no sense */ - result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; +/* result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; */ - result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC); + result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_zval_init(result->field_count, TRUE, result->persistent TSRMLS_CC); + if (!result->stored_data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + + ret = result->m.store_result_fetch_data(conn, result, result->meta, &result->stored_data->row_buffers, TRUE TSRMLS_CC); - ret = result->m.store_result_fetch_data(conn, result, result->meta, TRUE TSRMLS_CC); + result->stored_data->m.fetch_row = mysqlnd_stmt_fetch_row_buffered; if (PASS == ret) { + /* Overflow ? */ + if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + if (result->stored_data->row_count) { + /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ + if (result->stored_data->row_count * result->meta->field_count * sizeof(zval *) > SIZE_MAX) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */ + set->data = mnd_emalloc((size_t)(result->stored_data->row_count * result->meta->field_count * sizeof(zval))); + if (!set->data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + memset(set->data, 0, (size_t)(result->stored_data->row_count * result->meta->field_count * sizeof(zval))); + } + /* Position at the first row */ + set->data_cursor = set->data; + } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + /*TODO*/ + } + /* libmysql API docs say it should be so for SELECT statements */ stmt->upsert_status->affected_rows = stmt->result->stored_data->row_count; @@ -171,7 +190,7 @@ MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s TSRMLS_DC) break; } - if ((result = result->m.store_result(result, conn, TRUE TSRMLS_CC))) { + if ((result = result->m.store_result(result, conn, MYSQLND_STORE_PS | MYSQLND_STORE_NO_COPY TSRMLS_CC))) { stmt->upsert_status->affected_rows = result->stored_data->row_count; stmt->state = MYSQLND_STMT_PREPARED; result->type = MYSQLND_RES_PS_BUF; @@ -226,7 +245,7 @@ MYSQLND_METHOD(mysqlnd_stmt, next_result)(MYSQLND_STMT * s TSRMLS_DC) DBG_INF_FMT("server_status=%u cursor=%u", stmt->upsert_status->server_status, stmt->upsert_status->server_status & SERVER_STATUS_CURSOR_EXISTS); /* Free space for next result */ - s->m->free_stmt_content(s TSRMLS_CC); + s->m->free_stmt_result(s TSRMLS_CC); { enum_func_status ret = s->m->parse_execute_response(s TSRMLS_CC); DBG_RETURN(ret); @@ -486,6 +505,7 @@ mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s TSRMLS_DC) ret = mysqlnd_query_read_result_set_header(stmt->conn, s TSRMLS_CC); if (ret == FAIL) { COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info); + memset(stmt->upsert_status, 0, sizeof(*stmt->upsert_status)); stmt->upsert_status->affected_rows = conn->upsert_status->affected_rows; if (CONN_GET_STATE(conn) == CONN_QUIT_SENT) { /* close the statement here, the connection has been closed */ @@ -520,7 +540,11 @@ mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s TSRMLS_DC) /* Update stmt->field_count as SHOW sets it to 0 at prepare */ stmt->field_count = stmt->result->field_count = conn->field_count; - stmt->result->lengths = NULL; + if (stmt->result->stored_data) { + stmt->result->stored_data->lengths = NULL; + } else if (stmt->result->unbuf) { + stmt->result->unbuf->lengths = NULL; + } if (stmt->field_count) { stmt->state = MYSQLND_STMT_WAITING_USE_OR_STORE; /* @@ -619,11 +643,13 @@ MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const s TSRMLS_DC) fetch(); <-- no binding, but that's not a problem bind_result(); execute(); <-- here we will leak because we separate without need - */ + */ unsigned int i; for (i = 0; i < stmt->field_count; i++) { if (stmt->result_bind[i].bound == TRUE) { - zval_copy_ctor(stmt->result_bind[i].zv); + zval *result = &stmt->result_bind[i].zv; + ZVAL_DEREF(result); + Z_TRY_ADDREF_P(result); } } } @@ -657,7 +683,7 @@ MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const s TSRMLS_DC) DBG_RETURN(FAIL); } for (i = 0; i < stmt->param_count; i++) { - if (stmt->param_bind[i].zv == NULL) { + if (Z_ISUNDEF(stmt->param_bind[i].zv)) { not_bound++; } } @@ -708,94 +734,98 @@ MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const s TSRMLS_DC) /* {{{ mysqlnd_stmt_fetch_row_buffered */ enum_func_status -mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { MYSQLND_STMT * s = (MYSQLND_STMT *) param; MYSQLND_STMT_DATA * stmt = s? s->data:NULL; - MYSQLND_RES_BUFFERED *set = result->stored_data; - unsigned int field_count = result->meta->field_count; + const MYSQLND_RES_METADATA * const meta = result->meta; + unsigned int field_count = meta->field_count; DBG_ENTER("mysqlnd_stmt_fetch_row_buffered"); *fetched_anything = FALSE; DBG_INF_FMT("stmt=%lu", stmt != NULL ? stmt->stmt_id : 0L); /* If we haven't read everything */ - if (set->data_cursor && - (set->data_cursor - set->data) < (set->row_count * field_count)) - { - /* The user could have skipped binding - don't crash*/ - if (stmt->result_bind) { - unsigned int i; - MYSQLND_RES_METADATA * meta = result->meta; - zval **current_row = set->data_cursor; - - if (NULL == current_row[0]) { - uint64_t row_num = (set->data_cursor - set->data) / field_count; - enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num], - current_row, - meta->field_count, - meta->fields, - result->conn->options->int_and_float_native, - result->conn->stats TSRMLS_CC); - if (PASS != rc) { - DBG_RETURN(FAIL); - } - set->initialized_rows++; - if (stmt->update_max_length) { - for (i = 0; i < result->field_count; i++) { - /* - NULL fields are 0 length, 0 is not more than 0 - String of zero size, definitely can't be the next max_length. - Thus for NULL and zero-length we are quite efficient. - */ - if (Z_TYPE_P(current_row[i]) >= IS_STRING) { - unsigned long len = Z_STRLEN_P(current_row[i]); - if (meta->fields[i].max_length < len) { - meta->fields[i].max_length = len; + if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + if (set->data_cursor && + (set->data_cursor - set->data) < (result->stored_data->row_count * field_count)) + { + /* The user could have skipped binding - don't crash*/ + if (stmt->result_bind) { + unsigned int i; + zval *current_row = set->data_cursor; + + if (Z_ISUNDEF(current_row[0])) { + uint64_t row_num = (set->data_cursor - set->data) / field_count; + enum_func_status rc = result->stored_data->m.row_decoder(result->stored_data->row_buffers[row_num], + current_row, + meta->field_count, + meta->fields, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (PASS != rc) { + DBG_RETURN(FAIL); + } + result->stored_data->initialized_rows++; + if (stmt->update_max_length) { + for (i = 0; i < result->field_count; i++) { + /* + NULL fields are 0 length, 0 is not more than 0 + String of zero size, definitely can't be the next max_length. + Thus for NULL and zero-length we are quite efficient. + */ + if (Z_TYPE(current_row[i]) == IS_STRING) { + zend_ulong len = Z_STRLEN(current_row[i]); + if (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; + } } } } } - } - for (i = 0; i < result->field_count; i++) { - /* Clean what we copied last time */ + for (i = 0; i < result->field_count; i++) { + zval *result = &stmt->result_bind[i].zv; + + ZVAL_DEREF(result); + /* Clean what we copied last time */ #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - if (stmt->result_bind[i].zv) { - zval_dtor(stmt->result_bind[i].zv); - } + zval_dtor(result); #endif - /* copy the type */ - if (stmt->result_bind[i].bound == TRUE) { - DBG_INF_FMT("i=%u type=%u", i, Z_TYPE_P(current_row[i])); - if (Z_TYPE_P(current_row[i]) != IS_NULL) { - /* - Copy the value. - Pre-condition is that the zvals in the result_bind buffer - have been ZVAL_NULL()-ed or to another simple type - (int, double, bool but not string). Because of the reference - counting the user can't delete the strings the variables point to. - */ - - Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]); - stmt->result_bind[i].zv->value = current_row[i]->value; + /* copy the type */ + if (stmt->result_bind[i].bound == TRUE) { + DBG_INF_FMT("i=%u type=%u", i, Z_TYPE(current_row[i])); + if (Z_TYPE(current_row[i]) != IS_NULL) { + /* + Copy the value. + Pre-condition is that the zvals in the result_bind buffer + have been ZVAL_NULL()-ed or to another simple type + (int, double, bool but not string). Because of the reference + counting the user can't delete the strings the variables point to. + */ + + ZVAL_COPY_VALUE(result, ¤t_row[i]); #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - zval_copy_ctor(stmt->result_bind[i].zv); + Z_TRY_ADDREF_P(result); #endif - } else { - ZVAL_NULL(stmt->result_bind[i].zv); + } else { + ZVAL_NULL(result); + } } } } + set->data_cursor += field_count; + *fetched_anything = TRUE; + /* buffered result sets don't have a connection */ + MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF); + DBG_INF("row fetched"); + } else { + set->data_cursor = NULL; + DBG_INF("no more data"); } - set->data_cursor += field_count; - *fetched_anything = TRUE; - /* buffered result sets don't have a connection */ - MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF); - DBG_INF("row fetched"); - } else { - set->data_cursor = NULL; - DBG_INF("no more data"); + } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) { + /*TODO*/ } DBG_INF("PASS"); DBG_RETURN(PASS); @@ -804,13 +834,14 @@ mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param, unsigned int f /* {{{ mysqlnd_stmt_fetch_row_unbuffered */ -static enum_func_status -mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +enum_func_status +mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { enum_func_status ret; MYSQLND_STMT * s = (MYSQLND_STMT *) param; MYSQLND_STMT_DATA * stmt = s? s->data:NULL; MYSQLND_PACKET_ROW * row_packet; + const MYSQLND_RES_METADATA * const meta = result->meta; DBG_ENTER("mysqlnd_stmt_fetch_row_unbuffered"); @@ -827,7 +858,7 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int DBG_ERR("command out of sync"); DBG_RETURN(FAIL); } - if (!(row_packet = result->row_packet)) { + if (!(row_packet = result->unbuf->row_packet)) { DBG_RETURN(FAIL); } @@ -836,20 +867,20 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int /* If we skip rows (stmt == NULL || stmt->result_bind == NULL) we have to - result->m.unbuffered_free_last_data() before it. The function returns always true. + result->unbuf->m.free_last_data() before it. The function returns always true. */ if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { unsigned int i, field_count = result->field_count; if (!row_packet->skip_extraction) { - result->m.unbuffered_free_last_data(result TSRMLS_CC); + result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC); result->unbuf->last_row_data = row_packet->fields; result->unbuf->last_row_buffer = row_packet->row_buffer; row_packet->fields = NULL; row_packet->row_buffer = NULL; - if (PASS != result->m.row_decoder(result->unbuf->last_row_buffer, + if (PASS != result->unbuf->m.row_decoder(result->unbuf->last_row_buffer, result->unbuf->last_row_data, row_packet->field_count, row_packet->fields_metadata, @@ -861,19 +892,23 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int for (i = 0; i < field_count; i++) { if (stmt->result_bind[i].bound == TRUE) { - zval *data = result->unbuf->last_row_data[i]; + zval *data = &result->unbuf->last_row_data[i]; + zval *result = &stmt->result_bind[i].zv; + + ZVAL_DEREF(result); /* stmt->result_bind[i].zv has been already destructed - in result->m.unbuffered_free_last_data() + in result->unbuf->m.free_last_data() */ #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - zval_dtor(stmt->result_bind[i].zv); + zval_dtor(result); #endif - if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) { - if ((Z_TYPE_P(data) == IS_STRING) && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) { - result->meta->fields[i].max_length = Z_STRLEN_P(data); + if (!Z_ISNULL_P(data)) { + if ((Z_TYPE_P(data) == IS_STRING) && + (meta->fields[i].max_length < (zend_ulong) Z_STRLEN_P(data))) { + meta->fields[i].max_length = Z_STRLEN_P(data); } - stmt->result_bind[i].zv->value = data->value; + ZVAL_COPY_VALUE(result, data); /* copied data, thus also the ownership. Thus null data */ ZVAL_NULL(data); } @@ -883,7 +918,7 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int } else { DBG_INF("skipping extraction"); /* - Data has been allocated and usually result->m.unbuffered_free_last_data() + Data has been allocated and usually result->unbuf->m.free_last_data() frees it but we can't call this function as it will cause problems with the bound variables. Thus we need to do part of what it does or Zend will report leaks. @@ -905,6 +940,7 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int DBG_INF("EOF"); /* Mark the connection as usable again */ result->unbuf->eof_reached = TRUE; + memset(result->conn->upsert_status, 0, sizeof(*result->conn->upsert_status)); result->conn->upsert_status->warning_count = row_packet->warning_count; result->conn->upsert_status->server_status = row_packet->server_status; /* @@ -957,7 +993,7 @@ MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s TSRMLS_DC) result = stmt->result; result->m.use_result(stmt->result, TRUE TSRMLS_CC); - result->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor: + result->unbuf->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor: mysqlnd_stmt_fetch_row_unbuffered; stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED; @@ -971,7 +1007,7 @@ MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s TSRMLS_DC) /* {{{ mysqlnd_fetch_row_cursor */ enum_func_status -mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { enum_func_status ret; MYSQLND_STMT * s = (MYSQLND_STMT *) param; @@ -995,7 +1031,7 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla DBG_ERR("command out of sync"); DBG_RETURN(FAIL); } - if (!(row_packet = result->row_packet)) { + if (!(row_packet = result->unbuf->row_packet)) { DBG_RETURN(FAIL); } @@ -1014,18 +1050,20 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla row_packet->skip_extraction = stmt->result_bind? FALSE:TRUE; + memset(stmt->upsert_status, 0, sizeof(*stmt->upsert_status)); if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { + const MYSQLND_RES_METADATA * const meta = result->meta; unsigned int i, field_count = result->field_count; if (!row_packet->skip_extraction) { - result->m.unbuffered_free_last_data(result TSRMLS_CC); + result->unbuf->m.free_last_data(result->unbuf, result->conn? result->conn->stats : NULL TSRMLS_CC); result->unbuf->last_row_data = row_packet->fields; result->unbuf->last_row_buffer = row_packet->row_buffer; row_packet->fields = NULL; row_packet->row_buffer = NULL; - if (PASS != result->m.row_decoder(result->unbuf->last_row_buffer, + if (PASS != result->unbuf->m.row_decoder(result->unbuf->last_row_buffer, result->unbuf->last_row_data, row_packet->field_count, row_packet->fields_metadata, @@ -1038,21 +1076,27 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla /* If no result bind, do nothing. We consumed the data */ for (i = 0; i < field_count; i++) { if (stmt->result_bind[i].bound == TRUE) { - zval *data = result->unbuf->last_row_data[i]; + zval *data = &result->unbuf->last_row_data[i]; + zval *result = &stmt->result_bind[i].zv; + + ZVAL_DEREF(result); /* stmt->result_bind[i].zv has been already destructed - in result->m.unbuffered_free_last_data() + in result->unbuf->m.free_last_data() */ #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - zval_dtor(stmt->result_bind[i].zv); + zval_dtor(result); #endif - DBG_INF_FMT("i=%u bound_var=%p type=%u refc=%u", i, stmt->result_bind[i].zv, - Z_TYPE_P(data), Z_REFCOUNT_P(stmt->result_bind[i].zv)); - if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data))) { - if ((Z_TYPE_P(data) == IS_STRING) && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) { - result->meta->fields[i].max_length = Z_STRLEN_P(data); + DBG_INF_FMT("i=%u bound_var=%p type=%u refc=%u", i, &stmt->result_bind[i].zv, + Z_TYPE_P(data), Z_REFCOUNTED(stmt->result_bind[i].zv)? + Z_REFCOUNT(stmt->result_bind[i].zv) : 0); + + if (!Z_ISNULL_P(data)) { + if ((Z_TYPE_P(data) == IS_STRING) && + (meta->fields[i].max_length < (zend_ulong) Z_STRLEN_P(data))) { + meta->fields[i].max_length = Z_STRLEN_P(data); } - stmt->result_bind[i].zv->value = data->value; + ZVAL_COPY_VALUE(result, data); /* copied data, thus also the ownership. Thus null data */ ZVAL_NULL(data); } @@ -1061,7 +1105,7 @@ mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int fla } else { DBG_INF("skipping extraction"); /* - Data has been allocated and usually result->m.unbuffered_free_last_data() + Data has been allocated and usually result->unbuf->m.free_last_data() frees it but we can't call this function as it will cause problems with the bound variables. Thus we need to do part of what it does or Zend will report leaks. @@ -1136,7 +1180,7 @@ MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const s, zend_bool * const fe SET_EMPTY_ERROR(*stmt->error_info); SET_EMPTY_ERROR(*stmt->conn->error_info); - DBG_INF_FMT("result_bind=%p separated_once=%u", stmt->result_bind, stmt->result_zvals_separated_once); + DBG_INF_FMT("result_bind=%p separated_once=%u", &stmt->result_bind, stmt->result_zvals_separated_once); /* The user might have not bound any variables for result. Do the binding once she does it. @@ -1149,8 +1193,10 @@ MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const s, zend_bool * const fe */ for (i = 0; i < stmt->result->field_count; i++) { if (stmt->result_bind[i].bound == TRUE) { - zval_dtor(stmt->result_bind[i].zv); - ZVAL_NULL(stmt->result_bind[i].zv); + zval *result = &stmt->result_bind[i].zv; + ZVAL_DEREF(result); + zval_dtor(result); + ZVAL_NULL(result); } } stmt->result_zvals_separated_once = TRUE; @@ -1256,7 +1302,7 @@ MYSQLND_METHOD(mysqlnd_stmt, flush)(MYSQLND_STMT * const s TSRMLS_DC) /* {{{ mysqlnd_stmt::send_long_data */ static enum_func_status MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const s, unsigned int param_no, - const char * const data, unsigned long length TSRMLS_DC) + const char * const data, zend_ulong length TSRMLS_DC) { MYSQLND_STMT_DATA * stmt = s? s->data:NULL; enum_func_status ret = FAIL; @@ -1406,9 +1452,7 @@ MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const s, MYSQLND_PA We may have the last reference, then call zval_ptr_dtor() or we may leak memory. Switching from bind_one_parameter to bind_parameters may result in zv being NULL */ - if (stmt->param_bind[i].zv) { - zval_ptr_dtor(&stmt->param_bind[i].zv); - } + zval_ptr_dtor(&stmt->param_bind[i].zv); } if (stmt->param_bind != param_bind) { s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC); @@ -1421,7 +1465,7 @@ MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const s, MYSQLND_PA DBG_INF_FMT("%u is of type %u", i, stmt->param_bind[i].type); /* Prevent from freeing */ /* Don't update is_ref, or we will leak during conversion */ - Z_ADDREF_P(stmt->param_bind[i].zv); + Z_TRY_ADDREF(stmt->param_bind[i].zv); stmt->param_bind[i].flags = 0; if (stmt->param_bind[i].type == MYSQL_TYPE_LONG_BLOB) { stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; @@ -1471,17 +1515,15 @@ MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter)(MYSQLND_STMT * const s, unsigne /* Prevent from freeing */ /* Don't update is_ref, or we will leak during conversion */ - Z_ADDREF_P(zv); + Z_TRY_ADDREF_P(zv); DBG_INF("Binding"); /* Release what we had, if we had */ - if (stmt->param_bind[param_no].zv) { - zval_ptr_dtor(&stmt->param_bind[param_no].zv); - } + zval_ptr_dtor(&stmt->param_bind[param_no].zv); if (type == MYSQL_TYPE_LONG_BLOB) { /* The client will use stmt_send_long_data */ stmt->param_bind[param_no].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; } - stmt->param_bind[param_no].zv = zv; + ZVAL_COPY_VALUE(&stmt->param_bind[param_no].zv, zv); stmt->param_bind[param_no].type = type; stmt->send_types_to_server = 1; @@ -1557,8 +1599,10 @@ MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const s, stmt->result_bind = result_bind; for (i = 0; i < stmt->field_count; i++) { /* Prevent from freeing */ - Z_ADDREF_P(stmt->result_bind[i].zv); - DBG_INF_FMT("ref of %p = %u", stmt->result_bind[i].zv, Z_REFCOUNT_P(stmt->result_bind[i].zv)); + Z_TRY_ADDREF(stmt->result_bind[i].zv); + + DBG_INF_FMT("ref of %p = %u", &stmt->result_bind[i].zv, + Z_REFCOUNTED(stmt->result_bind[i].zv)? Z_REFCOUNT(stmt->result_bind[i].zv) : 0); /* Don't update is_ref !!! it's not our job Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt @@ -1612,7 +1656,7 @@ MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned i if (!stmt->result_bind) { DBG_RETURN(FAIL); } - ALLOC_INIT_ZVAL(stmt->result_bind[param_no].zv); + ZVAL_NULL(&stmt->result_bind[param_no].zv); /* Don't update is_ref !!! it's not our job Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt @@ -1769,7 +1813,8 @@ MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s TSRMLS_DC) if (stmt->update_max_length && stmt->result->stored_data) { /* stored result, we have to update the max_length before we clone the meta data :( */ - stmt->result->m.initialize_result_set_rest(stmt->result TSRMLS_CC); + stmt->result->stored_data->m.initialize_result_set_rest(stmt->result->stored_data, stmt->result->meta, stmt->conn->stats, + stmt->conn->options->int_and_float_native TSRMLS_CC); } /* TODO: This implementation is kind of a hack, @@ -1786,8 +1831,7 @@ MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s TSRMLS_DC) break; } result->type = MYSQLND_RES_NORMAL; - result->m.fetch_row = result->m.fetch_row_normal_unbuffered; - result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + result->unbuf = mysqlnd_result_unbuffered_init(stmt->field_count, TRUE, result->persistent TSRMLS_CC); if (!result->unbuf) { break; } @@ -1835,7 +1879,7 @@ MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const s, } case STMT_ATTR_CURSOR_TYPE: { unsigned int ival = *(unsigned int *) value; - if (ival > (unsigned long) CURSOR_TYPE_READ_ONLY) { + if (ival > (zend_ulong) CURSOR_TYPE_READ_ONLY) { SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented"); DBG_INF("FAIL"); DBG_RETURN(FAIL); @@ -1883,10 +1927,10 @@ MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s, *(zend_bool *) value= stmt->update_max_length; break; case STMT_ATTR_CURSOR_TYPE: - *(unsigned long *) value= stmt->flags; + *(zend_ulong *) value= stmt->flags; break; case STMT_ATTR_PREFETCH_ROWS: - *(unsigned long *) value= stmt->prefetch_rows; + *(zend_ulong *) value= stmt->prefetch_rows; break; default: DBG_RETURN(FAIL); @@ -1977,29 +2021,12 @@ mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s TSRMLS_DC) for (i = 0; i < stmt->field_count; i++) { /* Let's try with no cache */ if (stmt->result_bind[i].bound == TRUE) { - DBG_INF_FMT("%u has refcount=%u", i, Z_REFCOUNT_P(stmt->result_bind[i].zv)); - /* - We have to separate the actual zval value of the bound - variable from our allocated zvals or we will face double-free - */ - if (Z_REFCOUNT_P(stmt->result_bind[i].zv) > 1) { -#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - zval_copy_ctor(stmt->result_bind[i].zv); -#endif - zval_ptr_dtor(&stmt->result_bind[i].zv); - } else { - /* - If it is a string, what is pointed will be freed - later in free_result(). We need to remove the variable to - which the user has lost reference. - */ -#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - ZVAL_NULL(stmt->result_bind[i].zv); -#endif - zval_ptr_dtor(&stmt->result_bind[i].zv); - } + DBG_INF_FMT("%u has refcount=%u", i, + Z_REFCOUNTED(stmt->result_bind[i].zv)? Z_REFCOUNT(stmt->result_bind[i].zv) : 0); + zval_ptr_dtor(&stmt->result_bind[i].zv); } } + s->m->free_result_bind(s, stmt->result_bind TSRMLS_CC); stmt->result_bind = NULL; @@ -2030,27 +2057,41 @@ mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s, unsigned int param */ /* Let's try with no cache */ if (stmt->result_bind[param_no].bound == TRUE) { - DBG_INF_FMT("%u has refcount=%u", param_no, Z_REFCOUNT_P(stmt->result_bind[param_no].zv)); - /* - We have to separate the actual zval value of the bound - variable from our allocated zvals or we will face double-free - */ - if (Z_REFCOUNT_P(stmt->result_bind[param_no].zv) > 1) { -#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - zval_copy_ctor(stmt->result_bind[param_no].zv); -#endif - zval_ptr_dtor(&stmt->result_bind[param_no].zv); - } else { - /* - If it is a string, what is pointed will be freed - later in free_result(). We need to remove the variable to - which the user has lost reference. - */ -#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - ZVAL_NULL(stmt->result_bind[param_no].zv); -#endif - zval_ptr_dtor(&stmt->result_bind[param_no].zv); - } + DBG_INF_FMT("%u has refcount=%u", param_no, + Z_REFCOUNTED(stmt->result_bind[param_no].zv)? + Z_REFCOUNT(stmt->result_bind[param_no].zv) : 0); + zval_ptr_dtor(&stmt->result_bind[param_no].zv); + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::free_stmt_result */ +static void +MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::free_stmt_result"); + if (!stmt) { + DBG_VOID_RETURN; + } + + /* + First separate the bound variables, which point to the result set, then + destroy the set. + */ + mysqlnd_stmt_separate_result_bind(s TSRMLS_CC); + /* Not every statement has a result set attached */ + if (stmt->result) { + stmt->result->m.free_result_internal(stmt->result TSRMLS_CC); + stmt->result = NULL; + } + if (stmt->error_info->error_list) { + zend_llist_clean(stmt->error_info->error_list); + mnd_pefree(stmt->error_info->error_list, s->persistent); + stmt->error_info->error_list = NULL; } DBG_VOID_RETURN; @@ -2082,30 +2123,13 @@ MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content)(MYSQLND_STMT * const s TSRMLS_DC If bind_one_parameter was used, but not everything was bound and nothing was fetched, then some `zv` could be NULL */ - if (stmt->param_bind[i].zv) { - zval_ptr_dtor(&stmt->param_bind[i].zv); - } + zval_ptr_dtor(&stmt->param_bind[i].zv); } s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC); stmt->param_bind = NULL; } - /* - First separate the bound variables, which point to the result set, then - destroy the set. - */ - mysqlnd_stmt_separate_result_bind(s TSRMLS_CC); - /* Not every statement has a result set attached */ - if (stmt->result) { - stmt->result->m.free_result_internal(stmt->result TSRMLS_CC); - stmt->result = NULL; - } - if (stmt->error_info->error_list) { - zend_llist_clean(stmt->error_info->error_list); - mnd_pefree(stmt->error_info->error_list, s->persistent); - stmt->error_info->error_list = NULL; - } - + s->m->free_stmt_result(s TSRMLS_CC); DBG_VOID_RETURN; } /* }}} */ @@ -2324,7 +2348,8 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_stmt) mysqlnd_stmt_execute_generate_request, mysqlnd_stmt_execute_parse_response, MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content), - MYSQLND_METHOD(mysqlnd_stmt, flush) + MYSQLND_METHOD(mysqlnd_stmt, flush), + MYSQLND_METHOD(mysqlnd_stmt, free_stmt_result) MYSQLND_CLASS_METHODS_END; |