sqlx_mysql/types/
float.rs

1use byteorder::{ByteOrder, LittleEndian};
2
3use crate::decode::Decode;
4use crate::encode::{Encode, IsNull};
5use crate::error::BoxDynError;
6use crate::protocol::text::ColumnType;
7use crate::types::Type;
8use crate::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef};
9
10fn real_compatible(ty: &MySqlTypeInfo) -> bool {
11    // NOTE: `DECIMAL` is explicitly excluded because floating-point numbers have different semantics.
12    matches!(ty.r#type, ColumnType::Float | ColumnType::Double)
13}
14
15impl Type<MySql> for f32 {
16    fn type_info() -> MySqlTypeInfo {
17        MySqlTypeInfo::binary(ColumnType::Float)
18    }
19
20    fn compatible(ty: &MySqlTypeInfo) -> bool {
21        real_compatible(ty)
22    }
23}
24
25impl Type<MySql> for f64 {
26    fn type_info() -> MySqlTypeInfo {
27        MySqlTypeInfo::binary(ColumnType::Double)
28    }
29
30    fn compatible(ty: &MySqlTypeInfo) -> bool {
31        real_compatible(ty)
32    }
33}
34
35impl Encode<'_, MySql> for f32 {
36    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
37        buf.extend(&self.to_le_bytes());
38
39        Ok(IsNull::No)
40    }
41}
42
43impl Encode<'_, MySql> for f64 {
44    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
45        buf.extend(&self.to_le_bytes());
46
47        Ok(IsNull::No)
48    }
49}
50
51impl Decode<'_, MySql> for f32 {
52    fn decode(value: MySqlValueRef<'_>) -> Result<Self, BoxDynError> {
53        Ok(match value.format() {
54            MySqlValueFormat::Binary => {
55                let buf = value.as_bytes()?;
56
57                match buf.len() {
58                    // These functions panic if `buf` is not exactly the right size.
59                    4 => LittleEndian::read_f32(buf),
60                    // MySQL can return 8-byte DOUBLE values for a FLOAT
61                    // We take and truncate to f32 as that's the same behavior as *in* MySQL,
62                    #[allow(clippy::cast_possible_truncation)]
63                    8 => LittleEndian::read_f64(buf) as f32,
64                    other => {
65                        // Users may try to decode a DECIMAL as floating point;
66                        // inform them why that's a bad idea.
67                        return Err(format!(
68                            "expected a FLOAT as 4 or 8 bytes, got {other} bytes; \
69                             note that decoding DECIMAL as `f32` is not supported \
70                             due to differing semantics"
71                        )
72                        .into());
73                    }
74                }
75            }
76
77            MySqlValueFormat::Text => value.as_str()?.parse()?,
78        })
79    }
80}
81
82impl Decode<'_, MySql> for f64 {
83    fn decode(value: MySqlValueRef<'_>) -> Result<Self, BoxDynError> {
84        Ok(match value.format() {
85            MySqlValueFormat::Binary => {
86                let buf = value.as_bytes()?;
87
88                // The `read_*` functions panic if `buf` is not exactly the right size.
89                match buf.len() {
90                    // Allow implicit widening here
91                    4 => LittleEndian::read_f32(buf) as f64,
92                    8 => LittleEndian::read_f64(buf),
93                    other => {
94                        // Users may try to decode a DECIMAL as floating point;
95                        // inform them why that's a bad idea.
96                        return Err(format!(
97                            "expected a DOUBLE as 4 or 8 bytes, got {other} bytes; \
98                             note that decoding DECIMAL as `f64` is not supported \
99                             due to differing semantics"
100                        )
101                        .into());
102                    }
103                }
104            }
105            MySqlValueFormat::Text => value.as_str()?.parse()?,
106        })
107    }
108}