From: Andrey Hristov Date: Thu, 20 Mar 2008 13:25:49 +0000 (+0000) Subject: - Don't modify the variables which are passed for parameter binding. X-Git-Tag: RELEASE_2_0_0a1~78 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0e884df573239f8128f46d550aa42d69032135a7;p=php - Don't modify the variables which are passed for parameter binding. We need to clone them, if there will be a transformation (convert_to_xxx) which will change the origin. - Make mysqlnd more compatible to libmysql, in this case if the execute of a statement fails set the state of the statement back to PREPARED - A test case to check the case of a failing statement. --- diff --git a/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt b/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt new file mode 100644 index 0000000000..75153cc095 --- /dev/null +++ b/ext/mysqli/tests/mysqli_stmt_datatype_change.phpt @@ -0,0 +1,151 @@ +--TEST-- +mysqli_stmt_bind_param() - playing with references +--SKIPIF-- + +--FILE-- +query("use $db"); + $c2->query("use $db"); + $c1->query("drop table if exists type_change"); + $c1->query("create table type_change(a int, b char(10))"); + $c1->query("insert into type_change values (1, 'one'), (2, 'two')"); + $s1 = $c1->prepare("select a from type_change order by a"); + var_dump($s1); + var_dump($s1->execute(), $s1->bind_result($col1)); + echo "---- Row 1\n"; + var_dump($s1->fetch()); + var_dump($col1); + echo "---- Row 2\n"; + var_dump($s1->fetch()); + var_dump($col1); + echo "---- Row 3\n"; + var_dump($s1->fetch()); + echo "----\n"; + + echo "ALTER\n"; + var_dump($c2->query("alter table type_change drop a")); + var_dump($s1->execute()); + var_dump($c1->error); + + echo "---- Row 1\n"; + var_dump($s1->fetch()); + var_dump($col1); + echo "---- Row 2\n"; + var_dump($s1->fetch()); + var_dump($col1); + echo "---- Row 3\n"; + var_dump($s1->fetch()); + echo "----\n"; + + echo "done!"; +?> +--EXPECTF-- +object(mysqli_stmt)#%d (%d) { + ["affected_rows"]=> + int(0) + ["insert_id"]=> + int(0) + ["num_rows"]=> + int(0) + ["param_count"]=> + int(0) + ["field_count"]=> + int(1) + ["errno"]=> + int(0) + ["error"]=> + string(0) "" + ["sqlstate"]=> + string(5) "00000" + ["id"]=> + int(1) +} +bool(true) +bool(true) +---- Row 1 +bool(true) +int(1) +---- Row 2 +bool(true) +int(2) +---- Row 3 +NULL +---- +ALTER +bool(true) +bool(false) +string(34) "Unknown column 'a' in 'field list'" +---- Row 1 +bool(false) +int(2) +---- Row 2 +bool(false) +int(2) +---- Row 3 +bool(false) +---- +done! +--UEXPECTF-- +object(mysqli_stmt)#%d (%d) { + [u"affected_rows"]=> + int(0) + [u"insert_id"]=> + int(0) + [u"num_rows"]=> + int(0) + [u"param_count"]=> + int(0) + [u"field_count"]=> + int(1) + [u"errno"]=> + int(0) + [u"error"]=> + unicode(0) "" + [u"sqlstate"]=> + unicode(5) "00000" + [u"id"]=> + int(1) +} +bool(true) +bool(true) +---- Row 1 +bool(true) +int(1) +---- Row 2 +bool(true) +int(2) +---- Row 3 +NULL +---- +ALTER +bool(true) +bool(false) +unicode(34) "Unknown column 'a' in 'field list'" +---- Row 1 +bool(false) +int(2) +---- Row 2 +bool(false) +int(2) +---- Row 3 +bool(false) +---- +done! diff --git a/ext/mysqlnd/mysqlnd.c b/ext/mysqlnd/mysqlnd.c index bda568bd19..82702254bc 100644 --- a/ext/mysqlnd/mysqlnd.c +++ b/ext/mysqlnd/mysqlnd.c @@ -591,6 +591,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn, if (hashed_details) { mnd_efree(hashed_details); } + errcode = CR_CONNECTION_ERROR; goto err; } @@ -748,7 +749,7 @@ PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn, conn->net.cmd_buffer.length = 128L*1024L; conn->net.cmd_buffer.buffer = mnd_pemalloc(conn->net.cmd_buffer.length, conn->persistent); - mysqlnd_local_infile_default(conn); + mysqlnd_local_infile_default(conn); { uint buf_size; buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to uint*/ @@ -805,14 +806,14 @@ err: if (errstr) { DBG_ERR_FMT("[%d] %.64s (trying to connect via %s)", errcode, errstr, conn->scheme); - SET_CLIENT_ERROR(conn->error_info, errcode, UNKNOWN_SQLSTATE, errstr); - + SET_CLIENT_ERROR(conn->error_info, errcode? errcode:CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, errstr); php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%d] %.64s (trying to connect via %s)", errcode, errstr, conn->scheme); - - mnd_efree(errstr); + /* no mnd_ since we don't allocate it */ + efree(errstr); } if (conn->scheme) { - mnd_pefree(conn->scheme, conn->persistent); + /* no mnd_ since we don't allocate it */ + pefree(conn->scheme, conn->persistent); conn->scheme = NULL; } @@ -1316,7 +1317,7 @@ MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_state)(MYSQLND * const conn TSRMLS_DC) { enum mysqlnd_connection_state state; DBG_ENTER("mysqlnd_conn::get_state"); - tsrm_mutex_lock(conn->LOCK_state); + tsrm_mutex_lock(conn->LOCK_state); state = conn->state; tsrm_mutex_unlock(conn->LOCK_state); DBG_RETURN(state); diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c index 94df285e66..d6b98d018c 100644 --- a/ext/mysqlnd/mysqlnd_ps.c +++ b/ext/mysqlnd/mysqlnd_ps.c @@ -297,7 +297,9 @@ mysqlnd_stmt_read_prepare_response(MYSQLND_STMT *stmt TSRMLS_DC) stmt->stmt_id = prepare_resp.stmt_id; stmt->warning_count = stmt->conn->upsert_status.warning_count = prepare_resp.warning_count; - stmt->field_count = stmt->conn->field_count = prepare_resp.field_count; + stmt->upsert_status.affected_rows = 0; + stmt->field_count = prepare_resp.field_count; + stmt->conn->field_count = 0; stmt->param_count = prepare_resp.param_count; PACKET_FREE_ALLOCA(prepare_resp); @@ -522,17 +524,14 @@ MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const stmt TSRMLS_DC) if (CONN_GET_STATE(conn) == CONN_QUIT_SENT) { /* close the statement here, the connection has been closed */ } + stmt->state = MYSQLND_STMT_PREPARED; } else { SET_EMPTY_ERROR(stmt->error_info); SET_EMPTY_ERROR(stmt->conn->error_info); stmt->send_types_to_server = 0; stmt->upsert_status = conn->upsert_status; stmt->state = MYSQLND_STMT_EXECUTED; - if (conn->last_query_type == QUERY_UPSERT) { - stmt->upsert_status = conn->upsert_status; - DBG_INF("PASS"); - DBG_RETURN(PASS); - } else if (conn->last_query_type == QUERY_LOAD_LOCAL) { + if (conn->last_query_type == QUERY_UPSERT || conn->last_query_type == QUERY_LOAD_LOCAL) { DBG_INF("PASS"); DBG_RETURN(PASS); } diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c index dfc2cecddf..078ec93024 100644 --- a/ext/mysqlnd/mysqlnd_ps_codec.c +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -650,14 +650,30 @@ void _mysqlnd_init_ps_subsystem() /* }}} */ +/* {{{ mysqlnd_stmt_copy_it */ +static void +mysqlnd_stmt_copy_it(zval *** copies, zval *original, uint param_count, uint current) +{ + if (!*copies) { + *copies = ecalloc(param_count, sizeof(zval *)); + } + MAKE_STD_ZVAL((*copies)[current]); + *(*copies)[current] = *original; + Z_SET_REFCOUNT_P((*copies)[current], 1); + zval_copy_ctor((*copies)[current]); +} +/* }}} */ + + /* {{{ mysqlnd_stmt_execute_store_params */ -void +static void mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uchar **p, size_t *buf_len, unsigned int null_byte_offset TSRMLS_DC) { unsigned int i = 0; unsigned left = (*buf_len - (*p - *buf)); unsigned int data_size = 0; + zval **copies = NULL;/* if there are different types */ /* 1. Store type information */ if (stmt->send_types_to_server) { @@ -689,26 +705,44 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch /* 2. Store data */ /* 2.1 Calculate how much space we need */ for (i = 0; i < stmt->param_count; i++) { + unsigned int j; + zval *the_var = stmt->param_bind[i].zv; if (stmt->param_bind[i].zv && Z_TYPE_P(stmt->param_bind[i].zv) == IS_NULL) { continue; } + for (j = i + 1; j < stmt->param_count; j++) { + if (stmt->param_bind[j].zv == stmt->param_bind[i].zv) { + /* Double binding of the same zval, make a copy */ + mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i); + break; + } + } switch (stmt->param_bind[i].type) { case MYSQL_TYPE_DOUBLE: data_size += 8; + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_DOUBLE) { + if (!copies || !copies[i]) { + mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i); + } + } break; #if SIZEOF_LONG==8 case MYSQL_TYPE_LONGLONG: data_size += 8; - break; #elif SIZEOF_LONG==4 case MYSQL_TYPE_LONG: data_size += 4; - break; #else #error "Should not happen" #endif + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG) { + if (!copies || !copies[i]) { + mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i); + } + } + break; case MYSQL_TYPE_LONG_BLOB: if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) { /* @@ -721,8 +755,25 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch break; case MYSQL_TYPE_VAR_STRING: data_size += 8; /* max 8 bytes for size */ - convert_to_string_ex(&stmt->param_bind[i].zv); - data_size += Z_STRLEN_P(stmt->param_bind[i].zv); +#if PHP_MAJOR_VERSION < 6 + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_STRING) +#elif PHP_MAJOR_VERSION >= 6 + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_STRING || + (UG(unicode) && Z_TYPE_P(stmt->param_bind[i].zv) == IS_UNICODE)) +#endif + { + if (!copies || !copies[i]) { + mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i); + } + the_var = copies[i]; +#if PHP_MAJOR_VERSION >= 6 + if (UG(unicode) && Z_TYPE_P(the_var) == IS_UNICODE) { + zval_unicode_to_string_ex(the_var, UG(utf8_conv) TSRMLS_CC); + } +#endif + } + convert_to_string_ex(&the_var); + data_size += Z_STRLEN_P(the_var); break; } @@ -743,7 +794,7 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch /* 2.3 Store the actual data */ for (i = 0; i < stmt->param_count; i++) { - zval *data = stmt->param_bind[i].zv; + zval *data = copies && copies[i]? copies[i]: stmt->param_bind[i].zv; /* Handle long data */ if (stmt->param_bind[i].zv && Z_TYPE_P(data) == IS_NULL) { (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); @@ -791,7 +842,6 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch memcpy(*p, Z_STRVAL_P(data), len); (*p) += len; } - break; default: /* Won't happen, but set to NULL */ @@ -800,6 +850,14 @@ mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uch } } } + if (copies) { + for (i = 0; i < stmt->param_count; i++) { + if (copies[i]) { + zval_ptr_dtor(&copies[i]); + } + } + efree(copies); + } } /* }}} */ diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c index f987ffba65..9815cc1097 100644 --- a/ext/mysqlnd/mysqlnd_result.c +++ b/ext/mysqlnd/mysqlnd_result.c @@ -518,8 +518,8 @@ mysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT *stmt TSRMLS_DC conn->last_query_type = QUERY_SELECT; CONN_SET_STATE(conn, CONN_FETCHING_DATA); /* PS has already allocated it */ + conn->field_count = rset_header.field_count; if (!stmt) { - conn->field_count = rset_header.field_count; result = conn->current_result= mysqlnd_result_init(rset_header.field_count, @@ -533,7 +533,6 @@ mysqlnd_query_read_result_set_header(MYSQLND *conn, MYSQLND_STMT *stmt TSRMLS_DC prepared statements can't send result set metadata for these queries on prepare stage. Read it now. */ - conn->field_count = rset_header.field_count; result = stmt->result = mysqlnd_result_init(rset_header.field_count,