Skip to content

Commit be30a0f

Browse files
committed
Fix handling of numeric types for mysql
Newer libmariadb versions do perform not that much conversion logic on requested binds, so does for example `select 1/2` result in `0.5`. As result of a prior change we already have most of the logic to do the correct conversion in diesel in place. As part of this change we request change now the requested type to the actual field type as returned by libmysqlclient/libmariadb and do the conversion ourself as long as we are sure that the types are compatible. Additionally the conversion code now handles cases where we receive a number that would not be representable using the target type. For such cases a `Numeric overflow/underflow` error is returned
1 parent 681342f commit be30a0f

5 files changed

Lines changed: 228 additions & 33 deletions

File tree

diesel/src/mysql/connection/bind.rs

Lines changed: 153 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::mem;
33
use std::ops::Index;
44
use std::os::raw as libc;
55

6+
use super::stmt::MysqlFieldMetadata;
67
use super::stmt::Statement;
78
use crate::mysql::connection::stmt::StatementMetadata;
89
use crate::mysql::types::MYSQL_TIME;
@@ -31,13 +32,7 @@ impl Binds {
3132
.fields()
3233
.iter()
3334
.zip(types.into_iter().chain(std::iter::repeat(None)))
34-
.map(|(field, tpe)| {
35-
if let Some(tpe) = tpe {
36-
BindData::for_output(tpe.into())
37-
} else {
38-
BindData::for_output((field.field_type(), field.flags()))
39-
}
40-
})
35+
.map(|(field, tpe)| BindData::for_output(tpe, field))
4136
.collect();
4237

4338
Binds { data }
@@ -152,7 +147,156 @@ impl BindData {
152147
}
153148
}
154149

155-
fn for_output((tpe, flags): (ffi::enum_field_types, Flags)) -> Self {
150+
fn for_output(tpe: Option<MysqlType>, metadata: &MysqlFieldMetadata) -> Self {
151+
let (tpe, flags) = if let Some(tpe) = tpe {
152+
match (tpe, metadata.field_type()) {
153+
// Those are types where we handle the conversion in diesel itself
154+
// and do not relay on libmysqlclient
155+
(MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
156+
| (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_TINY)
157+
| (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_SHORT)
158+
| (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_LONG)
159+
| (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
160+
| (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
161+
| (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_INT24)
162+
| (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
163+
| (MysqlType::Tiny, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
164+
| (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
165+
| (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_TINY)
166+
| (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_SHORT)
167+
| (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_LONG)
168+
| (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
169+
| (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
170+
| (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_INT24)
171+
| (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
172+
| (MysqlType::UnsignedTiny, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
173+
| (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
174+
| (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_TINY)
175+
| (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_SHORT)
176+
| (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_LONG)
177+
| (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
178+
| (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
179+
| (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_INT24)
180+
| (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
181+
| (MysqlType::Short, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
182+
| (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
183+
| (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_TINY)
184+
| (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_SHORT)
185+
| (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_LONG)
186+
| (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
187+
| (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
188+
| (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_INT24)
189+
| (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
190+
| (MysqlType::UnsignedShort, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
191+
| (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
192+
| (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_TINY)
193+
| (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_SHORT)
194+
| (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_LONG)
195+
| (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
196+
| (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
197+
| (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_INT24)
198+
| (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
199+
| (MysqlType::Long, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
200+
| (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
201+
| (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_TINY)
202+
| (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_SHORT)
203+
| (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_LONG)
204+
| (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
205+
| (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
206+
| (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_INT24)
207+
| (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
208+
| (MysqlType::UnsignedLong, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
209+
| (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
210+
| (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_TINY)
211+
| (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_SHORT)
212+
| (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_LONG)
213+
| (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
214+
| (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
215+
| (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_INT24)
216+
| (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
217+
| (MysqlType::LongLong, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
218+
| (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
219+
| (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_TINY)
220+
| (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_SHORT)
221+
| (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_LONG)
222+
| (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
223+
| (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
224+
| (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_INT24)
225+
| (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
226+
| (MysqlType::UnsignedLongLong, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
227+
| (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
228+
| (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_TINY)
229+
| (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_SHORT)
230+
| (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_LONG)
231+
| (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
232+
| (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
233+
| (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_INT24)
234+
| (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
235+
| (MysqlType::Float, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
236+
| (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_DECIMAL)
237+
| (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_TINY)
238+
| (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_SHORT)
239+
| (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_LONG)
240+
| (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_FLOAT)
241+
| (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_DOUBLE)
242+
| (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_INT24)
243+
| (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_NEWDECIMAL)
244+
| (MysqlType::Numeric, ffi::enum_field_types::MYSQL_TYPE_LONGLONG)
245+
| (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_JSON)
246+
| (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_ENUM)
247+
| (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_SET)
248+
| (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB)
249+
| (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB)
250+
| (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB)
251+
| (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_BLOB)
252+
| (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING)
253+
| (MysqlType::String, ffi::enum_field_types::MYSQL_TYPE_STRING)
254+
| (MysqlType::Blob, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB)
255+
| (MysqlType::Blob, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB)
256+
| (MysqlType::Blob, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB)
257+
| (MysqlType::Blob, ffi::enum_field_types::MYSQL_TYPE_BLOB)
258+
| (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_ENUM)
259+
| (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_SET)
260+
| (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB)
261+
| (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB)
262+
| (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB)
263+
| (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_BLOB)
264+
| (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING)
265+
| (MysqlType::Set, ffi::enum_field_types::MYSQL_TYPE_STRING)
266+
| (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_ENUM)
267+
| (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_SET)
268+
| (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_TINY_BLOB)
269+
| (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_MEDIUM_BLOB)
270+
| (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_LONG_BLOB)
271+
| (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_BLOB)
272+
| (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_VAR_STRING)
273+
| (MysqlType::Enum, ffi::enum_field_types::MYSQL_TYPE_STRING) => {
274+
(metadata.field_type(), metadata.flags())
275+
}
276+
277+
(tpe, _) => tpe.into(),
278+
}
279+
} else {
280+
(metadata.field_type(), metadata.flags())
281+
};
282+
283+
let bytes = known_buffer_size_for_ffi_type(tpe)
284+
.map(|len| vec![0; len])
285+
.unwrap_or_default();
286+
let length = bytes.len() as libc::c_ulong;
287+
288+
BindData {
289+
tpe,
290+
bytes,
291+
length,
292+
is_null: 0,
293+
is_truncated: Some(0),
294+
flags,
295+
}
296+
}
297+
298+
#[cfg(test)]
299+
fn for_test_output((tpe, flags): (ffi::enum_field_types, Flags)) -> Self {
156300
let bytes = known_buffer_size_for_ffi_type(tpe)
157301
.map(|len| vec![0; len])
158302
.unwrap_or_default();
@@ -937,7 +1081,7 @@ mod tests {
9371081
) -> BindData {
9381082
let mut stmt: Statement = conn.raw_connection.prepare(query).unwrap();
9391083

940-
let bind = BindData::for_output(bind_tpe.into());
1084+
let bind = BindData::for_test_output(bind_tpe.into());
9411085

9421086
let mut binds = Binds { data: vec![bind] };
9431087

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use super::bind::{BindData, Binds};
1212
use crate::mysql::MysqlType;
1313
use crate::result::{DatabaseErrorKind, QueryResult};
1414

15-
pub use self::metadata::StatementMetadata;
15+
pub use self::metadata::{MysqlFieldMetadata, StatementMetadata};
1616

1717
pub struct Statement {
1818
stmt: NonNull<ffi::MYSQL_STMT>,

diesel/src/mysql/types/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ impl ToSql<Unsigned<SmallInt>, Mysql> for u16 {
109109

110110
impl FromSql<Unsigned<SmallInt>, Mysql> for u16 {
111111
fn from_sql(bytes: MysqlValue<'_>) -> deserialize::Result<Self> {
112-
let signed: i16 = FromSql::<SmallInt, Mysql>::from_sql(bytes)?;
112+
let signed: i32 = FromSql::<Integer, Mysql>::from_sql(bytes)?;
113113
Ok(signed as u16)
114114
}
115115
}
@@ -122,7 +122,7 @@ impl ToSql<Unsigned<Integer>, Mysql> for u32 {
122122

123123
impl FromSql<Unsigned<Integer>, Mysql> for u32 {
124124
fn from_sql(bytes: MysqlValue<'_>) -> deserialize::Result<Self> {
125-
let signed: i32 = FromSql::<Integer, Mysql>::from_sql(bytes)?;
125+
let signed: i64 = FromSql::<BigInt, Mysql>::from_sql(bytes)?;
126126
Ok(signed as u32)
127127
}
128128
}

diesel/src/mysql/types/primitives.rs

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
use std::error::Error;
2-
use std::str::{self, FromStr};
3-
41
use crate::deserialize::{self, FromSql};
52
use crate::mysql::{Mysql, MysqlValue};
3+
use crate::result::Error::DeserializationError;
64
use crate::sql_types::{BigInt, Binary, Double, Float, Integer, SmallInt, Text};
5+
use std::convert::TryInto;
6+
use std::error::Error;
7+
use std::str::{self, FromStr};
78

89
fn decimal_to_integer<T>(bytes: &[u8]) -> deserialize::Result<T>
910
where
@@ -13,32 +14,65 @@ where
1314
let string = str::from_utf8(bytes)?;
1415
let mut splited = string.split('.');
1516
let integer_portion = splited.next().unwrap_or_default();
16-
let decimal_portion = splited.next().unwrap_or_default();
17+
let _decimal_portion = splited.next().unwrap_or_default();
1718
if splited.next().is_some() {
1819
Err(format!("Invalid decimal format: {:?}", string).into())
19-
} else if decimal_portion.chars().any(|c| c != '0') {
20-
Err(format!(
21-
"Tried to convert a decimal to an integer that contained /
22-
a non null decimal portion: {:?}",
23-
string
24-
)
25-
.into())
2620
} else {
2721
Ok(integer_portion.parse()?)
2822
}
2923
}
3024

25+
fn f32_to_i64(f: f32) -> deserialize::Result<i64> {
26+
use std::i64;
27+
28+
if f <= i64::MAX as f32 && f >= i64::MIN as f32 {
29+
Ok(f.trunc() as i64)
30+
} else {
31+
Err(Box::new(DeserializationError(
32+
"Numeric overflow/underflow occurred".into(),
33+
)) as _)
34+
}
35+
}
36+
37+
fn f64_to_i64(f: f64) -> deserialize::Result<i64> {
38+
use std::i64;
39+
40+
if f <= i64::MAX as f64 && f >= i64::MIN as f64 {
41+
Ok(f.trunc() as i64)
42+
} else {
43+
Err(Box::new(DeserializationError(
44+
"Numeric overflow/underflow occurred".into(),
45+
)) as _)
46+
}
47+
}
48+
3149
impl FromSql<SmallInt, Mysql> for i16 {
3250
fn from_sql(value: MysqlValue<'_>) -> deserialize::Result<Self> {
3351
use crate::mysql::NumericRepresentation::*;
3452

3553
match value.numeric_value()? {
3654
Tiny(x) => Ok(x.into()),
3755
Small(x) => Ok(x),
38-
Medium(x) => Ok(x as Self),
39-
Big(x) => Ok(x as Self),
40-
Float(x) => Ok(x as Self),
41-
Double(x) => Ok(x as Self),
56+
Medium(x) => x.try_into().map_err(|_| {
57+
Box::new(DeserializationError(
58+
"Numeric overflow/underflow occurred".into(),
59+
)) as _
60+
}),
61+
Big(x) => x.try_into().map_err(|_| {
62+
Box::new(DeserializationError(
63+
"Numeric overflow/underflow occured".into(),
64+
)) as _
65+
}),
66+
Float(x) => f32_to_i64(x)?.try_into().map_err(|_| {
67+
Box::new(DeserializationError(
68+
"Numeric overflow/underflow occured".into(),
69+
)) as _
70+
}),
71+
Double(x) => f64_to_i64(x)?.try_into().map_err(|_| {
72+
Box::new(DeserializationError(
73+
"Numeric overflow/underflow occured".into(),
74+
)) as _
75+
}),
4276
Decimal(bytes) => decimal_to_integer(bytes),
4377
}
4478
}
@@ -52,9 +86,25 @@ impl FromSql<Integer, Mysql> for i32 {
5286
Tiny(x) => Ok(x.into()),
5387
Small(x) => Ok(x.into()),
5488
Medium(x) => Ok(x),
55-
Big(x) => Ok(x as Self),
56-
Float(x) => Ok(x as Self),
57-
Double(x) => Ok(x as Self),
89+
Big(x) => x.try_into().map_err(|_| {
90+
Box::new(DeserializationError(
91+
"Numeric overflow/underflow occured".into(),
92+
)) as _
93+
}),
94+
Float(x) => f32_to_i64(x).and_then(|i| {
95+
i.try_into().map_err(|_| {
96+
Box::new(DeserializationError(
97+
"Numeric overflow/underflow occured".into(),
98+
)) as _
99+
})
100+
}),
101+
Double(x) => f64_to_i64(x).and_then(|i| {
102+
i.try_into().map_err(|_| {
103+
Box::new(DeserializationError(
104+
"Numeric overflow/underflow occured".into(),
105+
)) as _
106+
})
107+
}),
58108
Decimal(bytes) => decimal_to_integer(bytes),
59109
}
60110
}
@@ -69,8 +119,8 @@ impl FromSql<BigInt, Mysql> for i64 {
69119
Small(x) => Ok(x.into()),
70120
Medium(x) => Ok(x.into()),
71121
Big(x) => Ok(x),
72-
Float(x) => Ok(x as Self),
73-
Double(x) => Ok(x as Self),
122+
Float(x) => f32_to_i64(x),
123+
Double(x) => f64_to_i64(x),
74124
Decimal(bytes) => decimal_to_integer(bytes),
75125
}
76126
}

diesel_tests/tests/boxed_queries.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ fn boxed_queries_can_differ_conditionally() {
3333
All,
3434
Ordered,
3535
One,
36-
};
36+
}
37+
3738
let source = |query| match query {
3839
Query::All => users::table.into_boxed(),
3940
Query::Ordered => users::table.order(users::name.desc()).into_boxed(),

0 commit comments

Comments
 (0)