Skip to content

Commit

Permalink
Add Decimal support for MySQL
Browse files Browse the repository at this point in the history
  • Loading branch information
Fiedzia authored and Eijebong committed Aug 3, 2017
1 parent 305b786 commit 8b069a1
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
`diesel::sqlite::SqliteConnection` are now exported from `diesel::prelude`.
You should no longer need to import these types explicitly.

* Added support for the Decimal datatype on MySQL, using the [BigDecimal crate][bigdecimal-0.16.0].

### Changed

* The deprecated `debug_sql!` and `print_sql!` functions will now generate
Expand All @@ -32,6 +34,8 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/

* `diesel::backend::Debug` has been removed.

[bigdecimal-0.14.0]: https://crates.io/crates/bigdecimal

## [0.15.2] - 2017-07-28

### Fixed
Expand Down
18 changes: 18 additions & 0 deletions diesel/src/mysql/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#[cfg(feature = "chrono")]
mod date_and_time;
mod numeric;

use byteorder::WriteBytesExt;
use mysql::{Mysql, MysqlType};
#[cfg(not(feature="postgres"))]
use query_builder::QueryId;
use std::error::Error as StdError;
use std::io::Write;
use types::{ToSql, ToSqlOutput, IsNull, FromSql, HasSqlType};
Expand Down Expand Up @@ -58,3 +61,18 @@ impl HasSqlType<::types::Timestamp> for Mysql {
MysqlType::Timestamp
}
}

impl HasSqlType<::types::Numeric> for Mysql {
fn metadata(_: &()) -> MysqlType {
MysqlType::String
}
}

#[cfg(not(feature="postgres"))]
impl QueryId for ::types::Numeric {
type QueryId = Self;

fn has_static_query_id() -> bool {
true
}
}
35 changes: 35 additions & 0 deletions diesel/src/mysql/types/numeric.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#[cfg(feature="bigdecimal")]
pub mod bigdecimal {
extern crate bigdecimal;

use std::error::Error;
use std::io::prelude::*;

use mysql::{Mysql, MysqlType};

use self::bigdecimal::BigDecimal;

use types::{self, FromSql, ToSql, ToSqlOutput, IsNull, HasSqlType};

impl ToSql<types::Numeric, Mysql> for BigDecimal {
fn to_sql<W: Write>(&self, out: &mut ToSqlOutput<W, Mysql>) -> Result<IsNull, Box<Error+Send+Sync>> {
write!(out, "{}", *self)
.map(|_| IsNull::No)
.map_err(|e| e.into())
}
}

impl FromSql<types::Numeric, Mysql> for BigDecimal {
fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<Error+Send+Sync>> {
let bytes = not_none!(bytes);
BigDecimal::parse_bytes(bytes, 10)
.ok_or_else(|| Box::from(format!("{:?} is not valid decimal number ", bytes)))
}
}

impl HasSqlType<BigDecimal> for Mysql {
fn metadata(_: &()) -> MysqlType {
MysqlType::String
}
}
}
5 changes: 3 additions & 2 deletions diesel/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,13 @@ use std::io::{self, Write};
///
/// ### [`ToSql`](/diesel/types/trait.ToSql.html) impls
///
/// - [`bigdecimal::BigDecimal`][bigdecimal] (currenty PostgreSQL only, requires the `numeric`
/// - [`bigdecimal::BigDecimal`][bigdecimal] (currenty PostgreSQL and MySQL only, requires the `numeric`
/// feature, which depends on the
/// [`bigdecimal`][bigdecimal] crate)
///
/// ### [`FromSql`](/diesel/types/trait.FromSql.html) impls
///
/// - [`bigdecimal::BigDecimal`][BigDecimal] (currenty PostgreSQL only, requires the `numeric`
/// - [`bigdecimal::BigDecimal`][BigDecimal] (currenty PostgreSQL and MySQL only, requires the `numeric`
/// feature, which depends on the
/// [`bigdecimal`][bigdecimal] crate)
///
Expand All @@ -163,6 +163,7 @@ use std::io::{self, Write};
/// [BigDecimal]: /bigdecimal/struct.BigDecimal.html
/// [bigdecimal]: /bigdecimal/index.html
#[derive(Debug, Clone, Copy, Default)] pub struct Numeric;
pub type Decimal = Numeric;

#[cfg(not(feature="postgres"))]
impl NotNull for Numeric {}
Expand Down
54 changes: 52 additions & 2 deletions diesel_tests/tests/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// FIXME: Review this module to see if we can do these casts in a more backend agnostic way
extern crate chrono;
#[cfg(feature="postgres")]
#[cfg(any(feature="postgres", feature="mysql"))]
extern crate bigdecimal;

use schema::*;
Expand Down Expand Up @@ -430,7 +430,7 @@ fn pg_numeric_from_sql() {
}

#[test]
#[cfg(feature = "postgres")]
#[cfg(feature="postgres")]
fn pg_numeric_bigdecimal_to_sql() {
use self::bigdecimal::BigDecimal;

Expand Down Expand Up @@ -462,6 +462,36 @@ fn pg_numeric_bigdecimal_to_sql() {
}
}

#[test]
#[cfg(feature="mysql")]
fn mysql_numeric_bigdecimal_to_sql() {
use self::bigdecimal::BigDecimal;

fn correct_rep(integer: u64, decimal: u64) -> bool {
let expected = format!("{}.{}", integer, decimal);
let value: BigDecimal = expected.parse().expect("Could not parse to a BigDecimal");
query_to_sql_equality::<Numeric, BigDecimal>(&expected, value)
}

quickcheck(correct_rep as fn(u64, u64) -> bool);

let test_values = vec![
"1.0",
"141.0",
"-1.0",
"10000",
"100000000",
"1.100001",
"10000.100001",
];

for value in test_values {
let expected = format!("cast('{}' as decimal(20, 10))", value);
let value = value.parse::<BigDecimal>().unwrap();
query_to_sql_equality::<Numeric, _>(&expected, value);
}
}

#[test]
#[cfg(feature = "postgres")]
fn pg_numeric_bigdecimal_from_sql() {
Expand All @@ -487,6 +517,26 @@ fn pg_numeric_bigdecimal_from_sql() {
}
}

#[test]
#[cfg(feature="mysql")]
fn mysql_numeric_bigdecimal_from_sql() {
use self::bigdecimal::BigDecimal;

let query = "cast(1.0 as decimal)";
let expected_value: BigDecimal = "1.0".parse().expect("Could not parse to a BigDecimal");
assert_eq!(expected_value, query_single_value::<Numeric, BigDecimal>(query));

let query = "cast(141.00 as decimal)";
let expected_value: BigDecimal = "141.00".parse().expect("Could not parse to a BigDecimal");
assert_eq!(expected_value, query_single_value::<Numeric, BigDecimal>(query));

// Some non standard values:
let query = "cast(18446744073709551616 as decimal)"; // 2^64; doesn't fit in u64
// It is mysql, it will trim it even in strict mode
let expected_value: BigDecimal = "9999999999.00".parse().expect("Could not parse to a BigDecimal");
assert_eq!(expected_value, query_single_value::<Numeric, BigDecimal>(query));
}

#[test]
#[cfg(feature = "postgres")]
fn pg_uuid_from_sql() {
Expand Down
13 changes: 8 additions & 5 deletions diesel_tests/tests/types_roundtrip.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
extern crate chrono;
#[cfg(any(feature = "postgres", feature = "mysql"))]
extern crate bigdecimal;

pub use quickcheck::quickcheck;
use self::chrono::{Duration, NaiveDate, NaiveDateTime, NaiveTime, DateTime, Utc};
Expand Down Expand Up @@ -90,7 +92,6 @@ mod sqlite_types {
mod pg_types {
extern crate uuid;
extern crate ipnetwork;
extern crate bigdecimal;

use super::*;

Expand All @@ -115,12 +116,8 @@ mod pg_types {
test_round_trip!(cidr_v6_roundtrips, Cidr, (u16, u16, u16, u16, u16, u16, u16, u16), mk_ipv6);
test_round_trip!(inet_v4_roundtrips, Inet, (u8, u8, u8, u8), mk_ipv4);
test_round_trip!(inet_v6_roundtrips, Inet, (u16, u16, u16, u16, u16, u16, u16, u16), mk_ipv6);

test_round_trip!(bigdecimal_roundtrips, Numeric, (i64, u64), mk_bigdecimal);

fn mk_bigdecimal(data: (i64, u64)) -> self::bigdecimal::BigDecimal {
format!("{}.{}", data.0, data.1).parse().expect("Could not interpret as bigdecimal")
}

fn mk_uuid(data: (u32, u16, u16, (u8, u8, u8, u8, u8, u8, u8, u8))) -> self::uuid::Uuid {
let a = data.3;
Expand Down Expand Up @@ -153,6 +150,7 @@ mod mysql_types {
test_round_trip!(naive_datetime_roundtrips, Timestamp, (i64, u32), mk_naive_datetime);
test_round_trip!(naive_time_roundtrips, Time, (u32, u32), mk_naive_time);
test_round_trip!(naive_date_roundtrips, Date, u32, mk_naive_date);
test_round_trip!(bigdecimal_roundtrips, Numeric, (i64, u64), mk_bigdecimal);
}

pub fn mk_naive_datetime(data: (i64, u32)) -> NaiveDateTime {
Expand All @@ -167,6 +165,11 @@ pub fn mk_datetime(data: (i64, u32)) -> DateTime<Utc> {
DateTime::from_utc(mk_naive_datetime(data), Utc)
}

#[cfg(any(feature = "postgres", feature = "mysql"))]
fn mk_bigdecimal(data: (i64, u64)) -> bigdecimal::BigDecimal {
format!("{}.{}", data.0, data.1).parse().expect("Could not interpret as bigdecimal")
}

#[cfg(feature = "postgres")]
pub fn mk_naive_date(days: u32) -> NaiveDate {
let earliest_pg_date = NaiveDate::from_ymd(-4713, 11, 24);
Expand Down

0 comments on commit 8b069a1

Please sign in to comment.