Skip to content

Commit 3fdf8e4

Browse files
committed
Call mysql_stmt_execute before mysql_stmt_result_metadata
Unlike PostgreSQL, we don't tell MySQL the types of the bind parameters when we prepare the statement. Instead, we do it at the point where the query is executed. That means that unless we've executed the query, MySQL cannot possibly know the return type of a query where there is a bind parameter in the select statement. This fixes the problem by moving the call to `mysql_stmt_result_metadata` to after the statement has been executed, but before the data has been loaded into the buffers.
1 parent acdf9ad commit 3fdf8e4

4 files changed

Lines changed: 46 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
1919
* Deprecated specifying a column name as `#[column_name(foo)]`. `#[column_name =
2020
"foo"]` should be used instead.
2121

22+
### Fixed
23+
24+
* `sql_query` now works properly on MySQL with queries where bind parameters
25+
appear in the select clause. Previously, attempting to do `SELECT ? AS id`
26+
would error if the bind parameter was any type other than a string.
27+
2228
## [1.0.0] - 2018-01-02
2329

2430
### Added

diesel/src/mysql/connection/stmt/iterator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ impl<'a> NamedRow<Mysql> for NamedMysqlRow<'a> {
125125
fn execute_statement(stmt: &mut Statement, binds: &mut Binds) -> QueryResult<()> {
126126
unsafe {
127127
binds.with_mysql_binds(|bind_ptr| stmt.bind_result(bind_ptr))?;
128-
stmt.execute()?;
128+
stmt.store_results()?;
129129
}
130130
Ok(())
131131
}

diesel/src/mysql/connection/stmt/mod.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,26 @@ impl Statement {
5656
/// have no return value. It should never be called on a statement on
5757
/// which `results` has previously been called?
5858
pub unsafe fn execute(&self) -> QueryResult<()> {
59+
self.execute_without_store()?;
60+
self.store_results()?;
61+
Ok(())
62+
}
63+
64+
/// This function should only be called internally by other functions which
65+
/// will later call `store_results`. Failure to do so will poison the
66+
/// connection.
67+
unsafe fn execute_without_store(&self) -> QueryResult<()> {
5968
ffi::mysql_stmt_execute(self.stmt);
60-
self.did_an_error_occur()?;
69+
self.did_an_error_occur()
70+
}
71+
72+
/// This method should only be called by the structs in `stmt::iterator`. It
73+
/// is only valid if `execute_without_store` was previously called. Calling
74+
/// this method any number of times other than exactly once per call to
75+
/// `execute_without_store` will poison the connection.
76+
pub unsafe fn store_results(&self) -> QueryResult<()> {
6177
ffi::mysql_stmt_store_result(self.stmt);
62-
self.did_an_error_occur()?;
63-
Ok(())
78+
self.did_an_error_occur()
6479
}
6580

6681
pub fn affected_rows(&self) -> usize {
@@ -72,13 +87,15 @@ impl Statement {
7287
/// have a return value. After calling this function, `execute` can never
7388
/// be called on this statement.
7489
pub unsafe fn results(&mut self, types: Vec<MysqlType>) -> QueryResult<StatementIterator> {
90+
self.execute_without_store()?;
7591
StatementIterator::new(self, types)
7692
}
7793

7894
/// This function should be called instead of `execute` for queries which
7995
/// have a return value. After calling this function, `execute` can never
8096
/// be called on this statement.
8197
pub unsafe fn named_results(&mut self) -> QueryResult<NamedStatementIterator> {
98+
self.execute_without_store()?;
8299
NamedStatementIterator::new(self)
83100
}
84101

diesel_tests/tests/raw_sql.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,22 @@ fn sql_query_can_take_bind_params() {
5252

5353
assert_eq!(Ok(expected), users);
5454
}
55+
56+
#[test]
57+
fn sql_query_can_deal_with_bind_params_in_select_clause() {
58+
use diesel::types::{Integer, Text};
59+
let conn = connection();
60+
61+
let query = if cfg!(feature = "postgres") {
62+
sql_query("SELECT $1 as id, $2 as name, $3 as hair_color")
63+
} else {
64+
sql_query("SELECT ? as id, ? as name, ? as hair_color")
65+
};
66+
let user = query.bind::<Integer, _>(1)
67+
.bind::<Text, _>("Sean")
68+
.bind::<Text, _>("black")
69+
.get_result(&conn);
70+
let expected = User::with_hair_color(1, "Sean", "black");
71+
72+
assert_eq!(Ok(expected), user);
73+
}

0 commit comments

Comments
 (0)