Skip to content

Commit 3388751

Browse files
authored
Merge pull request diesel-rs#652 from diesel-rs/sg-mysql-conversions
Have MySQL convert values to the expected type when possible
2 parents fc14d04 + c8bf33f commit 3388751

10 files changed

Lines changed: 63 additions & 88 deletions

File tree

diesel/src/macros/queryable.rs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -166,65 +166,56 @@ mod tests {
166166
use expression::dsl::sql;
167167
use prelude::*;
168168
use test_helpers::connection;
169-
170-
cfg_if! {
171-
if #[cfg(feature = "mysql")] {
172-
type IntLiteralSql = ::types::BigInt;
173-
type IntLiteralRust = i64;
174-
} else {
175-
type IntLiteralSql = ::types::Integer;
176-
type IntLiteralRust = i32;
177-
}
178-
}
169+
use types::Integer;
179170

180171
#[test]
181172
fn named_struct_definition() {
182173
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183174
struct MyStruct {
184-
foo: IntLiteralRust,
185-
bar: IntLiteralRust,
175+
foo: i32,
176+
bar: i32,
186177
}
187178

188179
impl_Queryable! {
189180
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190181
struct MyStruct {
191-
foo: IntLiteralRust,
192-
bar: IntLiteralRust,
182+
foo: i32,
183+
bar: i32,
193184
}
194185
}
195186

196187
let conn = connection();
197-
let data = ::select(sql::<(IntLiteralSql, IntLiteralSql)>("1, 2")).get_result(&conn);
188+
let data = ::select(sql::<(Integer, Integer)>("1, 2")).get_result(&conn);
198189
assert_eq!(Ok(MyStruct { foo: 1, bar: 2 }), data);
199190
}
200191

201192
#[test]
202193
fn tuple_struct() {
203194
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204-
struct MyStruct(IntLiteralRust, IntLiteralRust);
195+
struct MyStruct(i32, i32);
205196

206197
impl_Queryable! {
207198
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208-
struct MyStruct(#[column_name(foo)] IntLiteralRust, #[column_name(bar)] IntLiteralRust);
199+
struct MyStruct(#[column_name(foo)] i32, #[column_name(bar)] i32);
209200
}
210201

211202
let conn = connection();
212-
let data = ::select(sql::<(IntLiteralSql, IntLiteralSql)>("1, 2")).get_result(&conn);
203+
let data = ::select(sql::<(Integer, Integer)>("1, 2")).get_result(&conn);
213204
assert_eq!(Ok(MyStruct(1, 2)), data);
214205
}
215206

216207
#[test]
217208
fn tuple_struct_without_column_name_annotations() {
218209
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219-
struct MyStruct(IntLiteralRust, IntLiteralRust);
210+
struct MyStruct(i32, i32);
220211

221212
impl_Queryable! {
222213
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223-
struct MyStruct(IntLiteralRust, IntLiteralRust);
214+
struct MyStruct(i32, i32);
224215
}
225216

226217
let conn = connection();
227-
let data = ::select(sql::<(IntLiteralSql, IntLiteralSql)>("1, 2")).get_result(&conn);
218+
let data = ::select(sql::<(Integer, Integer)>("1, 2")).get_result(&conn);
228219
assert_eq!(Ok(MyStruct(1, 2)), data);
229220
}
230221
}

diesel/src/mysql/connection/bind.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ impl Binds {
2222
}
2323
}
2424

25-
pub fn from_output_types<Iter>(types: Iter) -> Self where
26-
Iter: IntoIterator<Item=ffi::enum_field_types>,
27-
{
28-
let data = types.into_iter().map(BindData::for_output).collect();
25+
pub fn from_output_types(types: Vec<MysqlType>) -> Self {
26+
let data = types.into_iter()
27+
.map(mysql_type_to_ffi_type)
28+
.map(BindData::for_output)
29+
.collect();
2930

3031
Binds {
3132
data: data,
@@ -43,6 +44,7 @@ impl Binds {
4344

4445
pub fn populate_dynamic_buffers(&mut self, stmt: &Statement) -> QueryResult<()> {
4546
for (i, data) in self.data.iter_mut().enumerate() {
47+
data.did_numeric_overflow_occur()?;
4648
// This is safe because we are re-binding the invalidated buffers
4749
// at the end of this function
4850
unsafe {
@@ -112,6 +114,10 @@ impl BindData {
112114
self.is_truncated.unwrap_or(0) != 0
113115
}
114116

117+
fn is_fixed_size_buffer(&self) -> bool {
118+
known_buffer_size_for_ffi_type(self.tpe).is_some()
119+
}
120+
115121
fn bytes(&self) -> Option<&[u8]> {
116122
if self.is_null == 0 {
117123
Some(&*self.bytes)
@@ -166,6 +172,16 @@ impl BindData {
166172
None
167173
}
168174
}
175+
176+
fn did_numeric_overflow_occur(&self) -> QueryResult<()> {
177+
use result::Error::DeserializationError;
178+
179+
if self.is_truncated() && self.is_fixed_size_buffer() {
180+
Err(DeserializationError("Numeric overflow/underflow occurred".into()))
181+
} else {
182+
Ok(())
183+
}
184+
}
169185
}
170186

171187
fn mysql_type_to_ffi_type(tpe: MysqlType) -> ffi::enum_field_types {

diesel/src/mysql/connection/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
mod bind;
22
mod raw;
3-
mod result;
43
mod stmt;
54
mod url;
65

@@ -62,7 +61,9 @@ impl Connection for MysqlConnection {
6261

6362
let mut stmt = try!(self.prepare_query(&source.as_query()));
6463
stmt.execute()?;
65-
stmt.results()?.map(|mut row| {
64+
let mut metadata = Vec::new();
65+
Mysql::row_metadata(&mut metadata);
66+
stmt.results(metadata)?.map(|mut row| {
6667
U::Row::build_from_row(&mut row)
6768
.map(U::build)
6869
.map_err(DeserializationError)

diesel/src/mysql/connection/result.rs

Lines changed: 0 additions & 37 deletions
This file was deleted.

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

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
11
use super::{Statement, Binds, ffi, libc};
22
use result::QueryResult;
33
use row::Row;
4-
use mysql::Mysql;
4+
use mysql::{Mysql, MysqlType};
55

66
pub struct StatementIterator<'a> {
77
stmt: &'a mut Statement,
88
output_binds: Binds,
99
}
1010

1111
impl<'a> StatementIterator<'a> {
12-
pub fn new(stmt: &'a mut Statement) -> QueryResult<Self> {
13-
use result::Error::QueryBuilderError;
14-
15-
let mut result_metadata = match try!(stmt.result_metadata()) {
16-
Some(result) => result,
17-
None => return Err(QueryBuilderError("Attempted to get results \
18-
on a query with no results".into())),
19-
};
20-
let result_types = result_metadata.fields().map(|f| f.type_);
21-
let mut output_binds = Binds::from_output_types(result_types);
12+
pub fn new(stmt: &'a mut Statement, types: Vec<MysqlType>) -> QueryResult<Self> {
13+
let mut output_binds = Binds::from_output_types(types);
2214

2315
unsafe {
2416
output_binds.with_mysql_binds(|bind_ptr| stmt.bind_result(bind_ptr))?

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use mysql::MysqlType;
99
use result::{QueryResult, DatabaseErrorKind};
1010
use self::iterator::StatementIterator;
1111
use super::bind::Binds;
12-
use super::result::RawResult;
1312

1413
pub struct Statement {
1514
stmt: *mut ffi::MYSQL_STMT,
@@ -56,17 +55,8 @@ impl Statement {
5655
affected_rows as usize
5756
}
5857

59-
pub fn results(&mut self) -> QueryResult<StatementIterator> {
60-
StatementIterator::new(self)
61-
}
62-
63-
fn result_metadata(&self) -> QueryResult<Option<RawResult>> {
64-
let result = unsafe {
65-
let result_ptr = ffi::mysql_stmt_result_metadata(self.stmt);
66-
RawResult::from_raw(result_ptr)
67-
};
68-
self.did_an_error_occur()?;
69-
Ok(result)
58+
pub fn results(&mut self, types: Vec<MysqlType>) -> QueryResult<StatementIterator> {
59+
StatementIterator::new(self, types)
7060
}
7161

7262
fn last_error_message(&self) -> String {

diesel/src/types/impls/option.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ use types::{HasSqlType, FromSql, FromSqlRow, Nullable, ToSql, IsNull, NotNull};
1212
impl<T, DB> HasSqlType<Nullable<T>> for DB where
1313
DB: Backend + HasSqlType<T>, T: NotNull,
1414
{
15-
fn metadata() -> DB::TypeMetadata{
15+
fn metadata() -> DB::TypeMetadata {
1616
<DB as HasSqlType<T>>::metadata()
1717
}
18+
19+
fn row_metadata(out: &mut Vec<DB::TypeMetadata>) {
20+
<DB as HasSqlType<T>>::row_metadata(out)
21+
}
1822
}
1923

2024
impl<T> QueryId for Nullable<T> where

diesel/src/types/impls/tuples.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ macro_rules! tuple_impls {
2323
fn metadata() -> DB::TypeMetadata {
2424
unreachable!("Tuples should never implement `ToSql` directly");
2525
}
26+
27+
fn row_metadata(out: &mut Vec<DB::TypeMetadata>) {
28+
$(<DB as HasSqlType<$T>>::row_metadata(out);)+
29+
}
2630
}
2731

2832
impl<$($T),+> NotNull for ($($T,)+) {

diesel/src/types/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ pub use pg::types::sql_types::*;
251251

252252
pub trait HasSqlType<ST>: TypeMetadata {
253253
fn metadata() -> Self::TypeMetadata;
254+
255+
fn row_metadata(out: &mut Vec<Self::TypeMetadata>) {
256+
out.push(Self::metadata())
257+
}
254258
}
255259

256260
pub trait NotNull {

diesel_tests/tests/expressions/ops.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ fn adding_literal_to_column() {
1616
assert_eq!(Ok(expected_data), data);
1717
}
1818

19+
#[test]
20+
#[cfg(not(feature="sqlite"))] // FIXME: Does SQLite provide a way to detect overflow?
21+
fn overflow_returns_an_error_but_does_not_panic() {
22+
use schema::users::dsl::*;
23+
24+
let connection = connection_with_sean_and_tess_in_users_table();
25+
let query_result = users.select(id + i32::max_value()).load::<i32>(&connection);
26+
assert!(query_result.is_err(), "Integer overflow should have returned an error");
27+
}
28+
1929
#[test]
2030
fn adding_column_to_column() {
2131
use schema::users::dsl::*;

0 commit comments

Comments
 (0)