From db000fb7e0bbbd84e432a0a59944f0157e164c4a Mon Sep 17 00:00:00 2001 From: lovasoa Date: Thu, 10 Aug 2023 22:12:29 +0200 Subject: [PATCH] fix decoding mysql DECIMAL values as f64 fixes https://github.com/lovasoa/SQLpage/issues/57 --- sqlx-core/src/mysql/types/float.rs | 45 ++++++++++++++++-------------- sqlx-core/src/mysql/value.rs | 2 +- tests/mysql/types.rs | 11 +++++++- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/sqlx-core/src/mysql/types/float.rs b/sqlx-core/src/mysql/types/float.rs index 07a76895ab..b6d723142d 100644 --- a/sqlx-core/src/mysql/types/float.rs +++ b/sqlx-core/src/mysql/types/float.rs @@ -1,5 +1,3 @@ -use byteorder::{ByteOrder, LittleEndian}; - use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; @@ -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 for f32 { @@ -49,29 +50,31 @@ impl Encode<'_, MySql> for f64 { impl Decode<'_, MySql> for f32 { fn decode(value: MySqlValueRef<'_>) -> Result { - 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 = >::decode(value)?; + Ok(as_f64 as f32) } } impl Decode<'_, MySql> for f64 { fn decode(value: MySqlValueRef<'_>) -> Result { - 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 + } }) } } diff --git a/sqlx-core/src/mysql/value.rs b/sqlx-core/src/mysql/value.rs index 58cf69cdbf..3d35d5ed4d 100644 --- a/sqlx-core/src/mysql/value.rs +++ b/sqlx-core/src/mysql/value.rs @@ -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>, diff --git a/tests/mysql/types.rs b/tests/mysql/types.rs index c5784d0487..2ccea0661b 100644 --- a/tests/mysql/types.rs +++ b/tests/mysql/types.rs @@ -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 @@ -225,6 +229,11 @@ test_type!(bigdecimal( "CAST(12345.6789 AS DECIMAL(9, 4))" == "12345.6789".parse::().unwrap(), )); +test_type!(bigdecimal_as_f64( + MySql, + "CAST(25.25 as DECIMAL(9, 4))" == 25.25, +)); + #[cfg(feature = "decimal")] test_type!(decimal(MySql, "CAST(0 as DECIMAL(0, 0))" == sqlx_oldapi::types::Decimal::from_str("0").unwrap(),