Skip to content

Commit

Permalink
fix decoding mysql DECIMAL values as f64
Browse files Browse the repository at this point in the history
  • Loading branch information
lovasoa committed Aug 10, 2023
1 parent 5ace007 commit db000fb
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 23 deletions.
45 changes: 24 additions & 21 deletions sqlx-core/src/mysql/types/float.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use byteorder::{ByteOrder, LittleEndian};

use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
Expand All @@ -8,7 +6,10 @@ use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef};
use crate::types::Type;

fn real_compatible(ty: &MySqlTypeInfo) -> bool {
matches!(ty.r#type, ColumnType::Float | ColumnType::Double)
matches!(
ty.r#type,
ColumnType::Float | ColumnType::Double | ColumnType::NewDecimal
)
}

impl Type<MySql> for f32 {
Expand Down Expand Up @@ -49,29 +50,31 @@ impl Encode<'_, MySql> for f64 {

impl Decode<'_, MySql> for f32 {
fn decode(value: MySqlValueRef<'_>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
MySqlValueFormat::Binary => {
let buf = value.as_bytes()?;

if buf.len() == 8 {
// MySQL can return 8-byte DOUBLE values for a FLOAT
// We take and truncate to f32 as that's the same behavior as *in* MySQL
LittleEndian::read_f64(buf) as f32
} else {
LittleEndian::read_f32(buf)
}
}

MySqlValueFormat::Text => value.as_str()?.parse()?,
})
let as_f64 = <f64 as Decode<'_, MySql>>::decode(value)?;
Ok(as_f64 as f32)
}
}

impl Decode<'_, MySql> for f64 {
fn decode(value: MySqlValueRef<'_>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
MySqlValueFormat::Binary => LittleEndian::read_f64(value.as_bytes()?),
MySqlValueFormat::Text => value.as_str()?.parse()?,
Ok(match (value.format(), value.type_info.r#type) {
(MySqlValueFormat::Binary, ColumnType::Float | ColumnType::Double) => {
let buf = value.as_bytes()?;
match buf.len() {
4 => f32::from_le_bytes(buf.try_into()?) as f64,
8 => f64::from_le_bytes(buf.try_into()?),
_ => {
return Err(
format!("float value buffer of unexpected size: {:02X?}", buf).into(),
)
}
}
}
_ => {
let str_val = value.as_str()?;
let parsed = str_val.parse()?;
parsed
}
})
}
}
2 changes: 1 addition & 1 deletion sqlx-core/src/mysql/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct MySqlValue {
}

/// Implementation of [`ValueRef`] for MySQL.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct MySqlValueRef<'r> {
pub(crate) value: Option<&'r [u8]>,
pub(crate) row: Option<&'r Bytes>,
Expand Down
11 changes: 10 additions & 1 deletion tests/mysql/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ test_type!(i32(MySql, "2141512" == 2141512_i32));
test_type!(u64(MySql, "CAST(2141512 AS UNSIGNED)" == 2141512_u64));
test_type!(i64(MySql, "2141512" == 2141512_i64));

test_type!(f64(MySql, "3.14159265e0" == 3.14159265_f64));
test_type!(f64(
MySql,
"3.14159265e0" == 3.14159265_f64,
"25.25" == 25.25_f64,
));

// NOTE: This behavior can be very surprising. MySQL implicitly widens FLOAT bind parameters
// to DOUBLE. This results in the weirdness you see below. MySQL generally recommends to stay
Expand Down Expand Up @@ -225,6 +229,11 @@ test_type!(bigdecimal<sqlx_oldapi::types::BigDecimal>(
"CAST(12345.6789 AS DECIMAL(9, 4))" == "12345.6789".parse::<sqlx_oldapi::types::BigDecimal>().unwrap(),
));

test_type!(bigdecimal_as_f64<f64>(
MySql,
"CAST(25.25 as DECIMAL(9, 4))" == 25.25,
));

#[cfg(feature = "decimal")]
test_type!(decimal<sqlx_oldapi::types::Decimal>(MySql,
"CAST(0 as DECIMAL(0, 0))" == sqlx_oldapi::types::Decimal::from_str("0").unwrap(),
Expand Down

0 comments on commit db000fb

Please sign in to comment.