1use crate::Decimal;
2use diesel::{
3 deserialize::{self, FromSql},
4 mysql::Mysql,
5 serialize::{self, IsNull, Output, ToSql},
6 sql_types::Numeric,
7};
8use std::io::Write;
9use std::str::FromStr;
10
11#[cfg(feature = "diesel")]
12impl ToSql<Numeric, Mysql> for Decimal {
13 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Mysql>) -> serialize::Result {
14 write!(out, "{}", *self).map(|_| IsNull::No).map_err(|e| e.into())
15 }
16}
17
18#[cfg(feature = "diesel")]
19impl FromSql<Numeric, Mysql> for Decimal {
20 fn from_sql(numeric: diesel::mysql::MysqlValue) -> deserialize::Result<Self> {
21 let s = std::str::from_utf8(numeric.as_bytes())?;
25 Decimal::from_str(s).map_err(|e| e.into())
26 }
27}
28
29#[cfg(test)]
30mod tests {
31 use super::*;
32
33 struct Test {
34 value: Decimal,
35 }
36
37 struct NullableTest {
38 value: Option<Decimal>,
39 }
40
41 pub static TEST_DECIMALS: &[(u32, u32, &str, &str)] = &[
42 (1, 0, "1", "1"),
44 (6, 2, "1", "1.00"),
45 (6, 2, "9999.99", "9999.99"),
46 (35, 6, "3950.123456", "3950.123456"),
47 (10, 2, "3950.123456", "3950.12"),
48 (35, 6, "3950", "3950.000000"),
49 (4, 0, "3950", "3950"),
50 (35, 6, "0.1", "0.100000"),
51 (35, 6, "0.01", "0.010000"),
52 (35, 6, "0.001", "0.001000"),
53 (35, 6, "0.0001", "0.000100"),
54 (35, 6, "0.00001", "0.000010"),
55 (35, 6, "0.000001", "0.000001"),
56 (35, 6, "1", "1.000000"),
57 (35, 6, "-100", "-100.000000"),
58 (35, 6, "-123.456", "-123.456000"),
59 (35, 6, "119996.25", "119996.250000"),
60 (35, 6, "1000000", "1000000.000000"),
61 (35, 6, "9999999.99999", "9999999.999990"),
62 (35, 6, "12340.56789", "12340.567890"),
63 ];
64
65 fn get_mysql_url() -> String {
68 if let Ok(url) = std::env::var("MYSQL_URL") {
69 return url;
70 }
71 "mysql://root@127.0.0.1/mysql".to_string()
72 }
73
74 #[cfg(feature = "diesel")]
75 mod diesel_tests {
76 use super::*;
77 use diesel::deserialize::QueryableByName;
78 use diesel::prelude::*;
79 use diesel::row::NamedRow;
80 use diesel::sql_query;
81 use diesel::sql_types::Text;
82
83 impl QueryableByName<Mysql> for Test {
84 fn build<'a>(row: &impl NamedRow<'a, Mysql>) -> deserialize::Result<Self> {
85 let value = NamedRow::get(row, "value")?;
86 Ok(Test { value })
87 }
88 }
89
90 impl QueryableByName<Mysql> for NullableTest {
91 fn build<'a>(row: &impl NamedRow<'a, Mysql>) -> deserialize::Result<Self> {
92 let value = NamedRow::get(row, "value")?;
93 Ok(NullableTest { value })
94 }
95 }
96
97 #[test]
98 fn test_null() {
99 let mut connection = diesel::MysqlConnection::establish(&get_mysql_url()).expect("Establish connection");
100
101 let items: Vec<NullableTest> = sql_query("SELECT CAST(NULL AS DECIMAL) AS value")
103 .load(&mut connection)
104 .expect("Unable to query value");
105 let result = items.first().unwrap().value;
106 assert_eq!(None, result);
107 }
108
109 #[test]
110 fn read_numeric_type() {
111 let mut connection = diesel::MysqlConnection::establish(&get_mysql_url()).expect("Establish connection");
112 for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() {
113 let items: Vec<Test> = sql_query(format!(
114 "SELECT CAST('{}' AS DECIMAL({}, {})) AS value",
115 sent, precision, scale
116 ))
117 .load(&mut connection)
118 .expect("Unable to query value");
119 assert_eq!(
120 expected,
121 items.first().unwrap().value.to_string(),
122 "DECIMAL({}, {}) sent: {}",
123 precision,
124 scale,
125 sent
126 );
127 }
128 }
129
130 #[test]
131 fn write_numeric_type() {
132 let mut connection = diesel::MysqlConnection::establish(&get_mysql_url()).expect("Establish connection");
133 for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() {
134 let items: Vec<Test> =
135 sql_query(format!("SELECT CAST(? AS DECIMAL({}, {})) AS value", precision, scale))
136 .bind::<Text, _>(sent)
137 .load(&mut connection)
138 .expect("Unable to query value");
139 assert_eq!(
140 expected,
141 items.first().unwrap().value.to_string(),
142 "DECIMAL({}, {}) sent: {}",
143 precision,
144 scale,
145 sent
146 );
147 }
148 }
149 }
150}