Skip to content

Commit e496880

Browse files
committed
Unsigned types support for mysql.
1 parent 1daf258 commit e496880

9 files changed

Lines changed: 106 additions & 10 deletions

File tree

diesel/src/mysql/types/mod.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#[cfg(feature = "chrono")]
22
mod date_and_time;
33

4-
use mysql::{Mysql, MysqlType};
4+
use byteorder::{WriteBytesExt};
5+
use mysql::{Mysql, MysqlType, backend};
56
use std::error::Error as StdError;
67
use std::io::Write;
7-
use types::{ToSql, IsNull, FromSql, HasSqlType};
8+
use types::{ToSql, IsNull, FromSql, HasSqlType, Unsigned};
9+
use backend::Backend;
810

911
impl ToSql<::types::Bool, Mysql> for bool {
1012
fn to_sql<W: Write>(&self, out: &mut W) -> Result<IsNull, Box<StdError+Send+Sync>> {
@@ -40,3 +42,49 @@ impl HasSqlType<::types::Timestamp> for Mysql {
4042
MysqlType::Timestamp
4143
}
4244
}
45+
46+
impl FromSql<Unsigned<::types::SmallInt>, Mysql> for u16 {
47+
fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<StdError+Send+Sync>> {
48+
let value: i16 = FromSql::<::types::SmallInt, Mysql>::from_sql(bytes)?;
49+
Ok(value as u16)
50+
}
51+
}
52+
53+
impl ToSql<Unsigned<::types::SmallInt>, Mysql> for u16 {
54+
fn to_sql<W: Write>(&self, out: &mut W) -> Result<IsNull, Box<StdError+Send+Sync>> {
55+
out.write_u16::<<backend::Mysql as Backend>::ByteOrder>(*self)
56+
.map(|_| IsNull::No)
57+
.map_err(|e| Box::new(e) as Box<StdError+Send+Sync>)
58+
}
59+
}
60+
61+
impl FromSql<Unsigned<::types::Integer>, Mysql> for u32 {
62+
fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<StdError+Send+Sync>> {
63+
let value: i32 = FromSql::<::types::Integer, Mysql>::from_sql(bytes)?;
64+
Ok(value as u32)
65+
}
66+
}
67+
68+
impl ToSql<Unsigned<::types::Integer>, Mysql> for u32 {
69+
fn to_sql<W: Write>(&self, out: &mut W) -> Result<IsNull, Box<StdError+Send+Sync>> {
70+
out.write_u32::<<backend::Mysql as Backend>::ByteOrder>(*self)
71+
.map(|_| IsNull::No)
72+
.map_err(|e| Box::new(e) as Box<StdError+Send+Sync>)
73+
}
74+
}
75+
76+
impl FromSql<Unsigned<::types::BigInt>, Mysql> for u64 {
77+
fn from_sql(bytes: Option<&[u8]>) -> Result<Self, Box<StdError+Send+Sync>> {
78+
let value: i64 = FromSql::<::types::BigInt, Mysql>::from_sql(bytes)?;
79+
Ok(value as u64)
80+
}
81+
}
82+
83+
impl ToSql<Unsigned<::types::BigInt>, Mysql> for u64 {
84+
fn to_sql<W: Write>(&self, out: &mut W) -> Result<IsNull, Box<StdError+Send+Sync>> {
85+
out.write_u64::<<backend::Mysql as Backend>::ByteOrder>(*self)
86+
.map(|_| IsNull::No)
87+
.map_err(|e| Box::new(e) as Box<StdError+Send+Sync>)
88+
}
89+
}
90+

diesel/src/types/impls/primitives.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ primitive_impls!(Bool -> (bool, pg: (16, 1000), sqlite: (Integer), mysql: (Tiny)
99
primitive_impls!(SmallInt -> (i16, pg: (21, 1005), sqlite: (SmallInt), mysql: (Short)));
1010
primitive_impls!(Integer -> (i32, pg: (23, 1007), sqlite: (Integer), mysql: (Long)));
1111
primitive_impls!(BigInt -> (i64, pg: (20, 1016), sqlite: (Long), mysql: (LongLong)));
12+
primitive_impls!(UInt2 -> (u16, mysql: (Short)));
13+
primitive_impls!(UInt4 -> (u32, mysql: (Long)));
14+
primitive_impls!(UInt8 -> (u64, mysql: (LongLong)));
1215

1316
primitive_impls!(Float -> (f32, pg: (700, 1021), sqlite: (Float), mysql: (Float)));
1417
primitive_impls!(Double -> (f64, pg: (701, 1022), sqlite: (Double), mysql: (Double)));

diesel/src/types/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,12 @@ pub type VarChar = Text;
249249
/// - `Option<T>` for any `T` which implements `FromSql<ST>`
250250
#[derive(Debug, Clone, Copy, Default)] pub struct Nullable<ST: NotNull>(ST);
251251

252+
#[derive(Debug, Clone, Copy, Default)] pub struct Unsigned<ST>(ST);
253+
254+
#[doc(hidden)] pub type UInt2 = Unsigned<SmallInt>;
255+
#[doc(hidden)] pub type UInt4 = Unsigned<Integer>;
256+
#[doc(hidden)] pub type UInt8 = Unsigned<BigInt>;
257+
252258
#[cfg(feature = "postgres")]
253259
pub use pg::types::sql_types::*;
254260

diesel_infer_schema/src/codegen.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ fn column_def_tokens(
8484
};
8585
let mut tpe = quote!(#tpe);
8686

87+
if column_type.is_unsigned {
88+
tpe = quote!(Unsigned<#tpe>);
89+
}
8790
if column_type.is_array {
8891
tpe = quote!(Array<#tpe>);
8992
}

diesel_infer_schema/src/data_structures.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub struct ColumnType {
1919
pub path: Vec<String>,
2020
pub is_array: bool,
2121
pub is_nullable: bool,
22+
pub is_unsigned: bool,
2223
}
2324

2425
impl ColumnInformation {

diesel_infer_schema/src/mysql.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ use data_structures::*;
44

55
pub fn determine_column_type(attr: &ColumnInformation) -> Result<ColumnType, Box<Error>> {
66
let tpe = determine_type_name(&attr.type_name)?;
7+
let unsigned = determine_unsigned(&attr.type_name);
78

89
Ok(ColumnType {
9-
path: vec!["diesel".into(), "types".into(), capitalize(tpe)],
10+
path: vec!["diesel".into(), "types".into(), capitalize(tpe.trim())],
1011
is_array: false,
1112
is_nullable: attr.nullable,
13+
is_unsigned: unsigned,
1214
})
1315
}
1416

15-
fn determine_type_name(sql_type_name: &str) -> Result<&str, Box<Error>> {
17+
fn determine_type_name(sql_type_name: &str) -> Result<String, Box<Error>> {
1618
let result = if sql_type_name == "tinyint(1)" {
1719
"bool"
1820
} else if sql_type_name.starts_with("int") {
@@ -24,14 +26,19 @@ fn determine_type_name(sql_type_name: &str) -> Result<&str, Box<Error>> {
2426
};
2527

2628
if result.to_lowercase().contains("unsigned") {
27-
Err("unsigned types are not yet supported".into())
29+
let result = result.to_lowercase().replace("unsigned", "").trim().to_owned();
30+
Ok(result)
2831
} else if result.contains(' ') {
2932
Err(format!("unrecognized type {:?}", result).into())
3033
} else {
31-
Ok(result)
34+
Ok(result.to_owned())
3235
}
3336
}
3437

38+
fn determine_unsigned(sql_type_name: &str) -> bool {
39+
sql_type_name.to_lowercase().contains("unsigned")
40+
}
41+
3542
fn capitalize(name: &str) -> String {
3643
name[..1].to_uppercase() + &name[1..]
3744
}
@@ -63,10 +70,15 @@ fn int_is_treated_as_integer() {
6370
}
6471

6572
#[test]
66-
fn unsigned_types_are_not_supported() {
67-
assert!(determine_type_name("float unsigned").is_err());
68-
assert!(determine_type_name("UNSIGNED INT").is_err());
69-
assert!(determine_type_name("unsigned bigint").is_err())
73+
fn unsigned_types_are_supported() {
74+
assert!(determine_unsigned("float unsigned"));
75+
assert!(determine_unsigned("UNSIGNED INT"));
76+
assert!(determine_unsigned("unsigned bigint"));
77+
assert!(!determine_unsigned("bigint"));
78+
assert!(!determine_unsigned("FLOAT"));
79+
assert_eq!("float", determine_type_name("float unsigned").unwrap());
80+
assert_eq!("int", determine_type_name("UNSIGNED INT").unwrap());
81+
assert_eq!("bigint", determine_type_name("unsigned bigint").unwrap());
7082
}
7183

7284
#[test]

diesel_infer_schema/src/pg.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub fn determine_column_type(attr: &ColumnInformation) -> Result<ColumnType, Box
1414
path: vec!["diesel".into(), "types".into(), capitalize(tpe)],
1515
is_array: is_array,
1616
is_nullable: attr.nullable,
17+
is_unsigned: false,
1718
})
1819
}
1920

diesel_infer_schema/src/sqlite.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ pub fn determine_column_type(attr: &ColumnInformation) -> Result<ColumnType, Box
111111
path: path,
112112
is_array: false,
113113
is_nullable: attr.nullable,
114+
is_unsigned: false,
114115
})
115116
}
116117

diesel_tests/tests/types.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,27 @@ fn i32_to_sql_integer() {
9696
assert!(!query_to_sql_equality::<Integer, i32>("70000", 69999));
9797
}
9898

99+
#[test]
100+
#[cfg(feature = "mysql")]
101+
fn u32_to_sql_interger(){
102+
assert!(query_to_sql_equality::<Unsigned<Integer>, u32>("-1", 4294967295));
103+
assert!(query_to_sql_equality::<Unsigned<Integer>, u32>("0", 0));
104+
assert!(query_to_sql_equality::<Unsigned<Integer>, u32>("1", 1));
105+
assert!(query_to_sql_equality::<Unsigned<Integer>, u32>("70000", 70000));
106+
assert!(!query_to_sql_equality::<Unsigned<Integer>, u32>("0", 1));
107+
assert!(!query_to_sql_equality::<Unsigned<Integer>, u32>("70000", 69999));
108+
assert!(!query_to_sql_equality::<Unsigned<Integer>, u32>("-1", 4294967294));
109+
}
110+
111+
#[test]
112+
#[cfg(feature = "mysql")]
113+
fn u32_from_sql() {
114+
assert_eq!(0, query_single_value::<Unsigned<Integer>, u32>("0"));
115+
assert_eq!(4294967295, query_single_value::<Unsigned<Integer>, u32>("-1"));
116+
assert_ne!(4294967294, query_single_value::<Unsigned<Integer>, u32>("-1"));
117+
assert_eq!(70000, query_single_value::<Unsigned<Integer>, u32>("70000"));
118+
}
119+
99120
#[test]
100121
#[cfg(feature = "postgres")]
101122
fn i64_from_sql() {

0 commit comments

Comments
 (0)