Skip to content

Commit c297a7b

Browse files
authored
Merge pull request diesel-rs#1751 from diesel-rs/sg-fix-mysql-unsigned-no-breaking-change
Fix our handling of unsigned types on MySQL
2 parents 3ee24d3 + 44e3336 commit c297a7b

12 files changed

Lines changed: 159 additions & 38 deletions

File tree

diesel/src/mysql/backend.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
33
use byteorder::NativeEndian;
44

5+
use super::bind_collector::MysqlBindCollector;
56
use super::query_builder::MysqlQueryBuilder;
67
use backend::*;
7-
use query_builder::bind_collector::RawBytesBindCollector;
88
use sql_types::TypeMetadata;
99

1010
/// The MySQL backend
@@ -48,7 +48,7 @@ pub enum MysqlType {
4848

4949
impl Backend for Mysql {
5050
type QueryBuilder = MysqlQueryBuilder;
51-
type BindCollector = RawBytesBindCollector<Mysql>;
51+
type BindCollector = MysqlBindCollector;
5252
type RawValue = [u8];
5353
type ByteOrder = NativeEndian;
5454
}

diesel/src/mysql/bind_collector.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use super::{Mysql, MysqlType};
2+
use query_builder::BindCollector;
3+
use result::Error::SerializationError;
4+
use result::QueryResult;
5+
use serialize::{IsNull, Output, ToSql};
6+
use sql_types::{HasSqlType, IsSigned};
7+
8+
#[derive(Default)]
9+
#[doc(hidden)]
10+
#[allow(missing_debug_implementations)]
11+
pub struct MysqlBindCollector {
12+
pub(crate) binds: Vec<(MysqlType, IsSigned, Option<Vec<u8>>)>,
13+
}
14+
15+
impl MysqlBindCollector {
16+
pub(crate) fn new() -> Self {
17+
Self::default()
18+
}
19+
}
20+
21+
impl BindCollector<Mysql> for MysqlBindCollector {
22+
fn push_bound_value<T, U>(&mut self, bind: &U, metadata_lookup: &()) -> QueryResult<()>
23+
where
24+
Mysql: HasSqlType<T>,
25+
U: ToSql<T, Mysql>,
26+
{
27+
let mut to_sql_output = Output::new(Vec::new(), metadata_lookup);
28+
let is_null = bind.to_sql(&mut to_sql_output).map_err(SerializationError)?;
29+
let bytes = match is_null {
30+
IsNull::No => Some(to_sql_output.into_inner()),
31+
IsNull::Yes => None,
32+
};
33+
let metadata = Mysql::metadata(metadata_lookup);
34+
let sign = Mysql::is_signed();
35+
self.binds.push((metadata, sign, bytes));
36+
Ok(())
37+
}
38+
}

diesel/src/mysql/connection/bind.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::os::raw as libc;
66
use super::stmt::Statement;
77
use mysql::MysqlType;
88
use result::QueryResult;
9+
use sql_types::IsSigned;
910

1011
pub struct Binds {
1112
data: Vec<BindData>,
@@ -14,20 +15,20 @@ pub struct Binds {
1415
impl Binds {
1516
pub fn from_input_data<Iter>(input: Iter) -> Self
1617
where
17-
Iter: IntoIterator<Item = (MysqlType, Option<Vec<u8>>)>,
18+
Iter: IntoIterator<Item = (MysqlType, IsSigned, Option<Vec<u8>>)>,
1819
{
1920
let data = input
2021
.into_iter()
21-
.map(|(tpe, bytes)| BindData::for_input(tpe, bytes))
22+
.map(|(tpe, sign, bytes)| BindData::for_input(tpe, is_signed_to_my_bool(sign), bytes))
2223
.collect();
2324

2425
Binds { data: data }
2526
}
2627

27-
pub fn from_output_types(types: Vec<MysqlType>) -> Self {
28+
pub fn from_output_types(types: Vec<(MysqlType, IsSigned)>) -> Self {
2829
let data = types
2930
.into_iter()
30-
.map(mysql_type_to_ffi_type)
31+
.map(|(ty, sign)| (mysql_type_to_ffi_type(ty), is_signed_to_my_bool(sign)))
3132
.map(BindData::for_output)
3233
.collect();
3334

@@ -37,7 +38,7 @@ impl Binds {
3738
pub fn from_result_metadata(fields: &[ffi::MYSQL_FIELD]) -> Self {
3839
let data = fields
3940
.iter()
40-
.map(|field| field.type_)
41+
.map(|field| (field.type_, is_field_unsigned(field)))
4142
.map(BindData::for_output)
4243
.collect();
4344

@@ -89,10 +90,11 @@ struct BindData {
8990
length: libc::c_ulong,
9091
is_null: ffi::my_bool,
9192
is_truncated: Option<ffi::my_bool>,
93+
is_unsigned: ffi::my_bool,
9294
}
9395

9496
impl BindData {
95-
fn for_input(tpe: MysqlType, data: Option<Vec<u8>>) -> Self {
97+
fn for_input(tpe: MysqlType, is_unsigned: ffi::my_bool, data: Option<Vec<u8>>) -> Self {
9698
let is_null = if data.is_none() { 1 } else { 0 };
9799
let bytes = data.unwrap_or_default();
98100
let length = bytes.len() as libc::c_ulong;
@@ -103,10 +105,11 @@ impl BindData {
103105
length: length,
104106
is_null: is_null,
105107
is_truncated: None,
108+
is_unsigned,
106109
}
107110
}
108111

109-
fn for_output(tpe: ffi::enum_field_types) -> Self {
112+
fn for_output((tpe, is_unsigned): (ffi::enum_field_types, ffi::my_bool)) -> Self {
110113
let bytes = known_buffer_size_for_ffi_type(tpe)
111114
.map(|len| vec![0; len])
112115
.unwrap_or_default();
@@ -118,6 +121,7 @@ impl BindData {
118121
length: length,
119122
is_null: 0,
120123
is_truncated: Some(0),
124+
is_unsigned,
121125
}
122126
}
123127

@@ -151,6 +155,7 @@ impl BindData {
151155
bind.buffer_length = self.bytes.capacity() as libc::c_ulong;
152156
bind.length = &mut self.length;
153157
bind.is_null = &mut self.is_null;
158+
bind.is_unsigned = self.is_unsigned;
154159

155160
if let Some(ref mut is_truncated) = self.is_truncated {
156161
bind.error = is_truncated;
@@ -236,3 +241,15 @@ fn known_buffer_size_for_ffi_type(tpe: ffi::enum_field_types) -> Option<usize> {
236241
_ => None,
237242
}
238243
}
244+
245+
fn is_field_unsigned(field: &ffi::MYSQL_FIELD) -> ffi::my_bool {
246+
const UNSIGNED_FLAG: libc::c_uint = 32;
247+
(field.flags & UNSIGNED_FLAG > 0) as _
248+
}
249+
250+
fn is_signed_to_my_bool(sign: IsSigned) -> ffi::my_bool {
251+
match sign {
252+
IsSigned::Signed => false as _,
253+
IsSigned::Unsigned => true as _,
254+
}
255+
}

diesel/src/mysql/connection/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ use self::raw::RawConnection;
77
use self::stmt::Statement;
88
use self::url::ConnectionOptions;
99
use super::backend::Mysql;
10+
use super::bind_collector::MysqlBindCollector;
1011
use connection::*;
1112
use deserialize::{Queryable, QueryableByName};
12-
use query_builder::bind_collector::RawBytesBindCollector;
1313
use query_builder::*;
1414
use result::*;
1515
use sql_types::HasSqlType;
@@ -71,7 +71,7 @@ impl Connection for MysqlConnection {
7171

7272
let mut stmt = try!(self.prepare_query(&source.as_query()));
7373
let mut metadata = Vec::new();
74-
Mysql::row_metadata(&mut metadata, &());
74+
Mysql::mysql_row_metadata(&mut metadata, &());
7575
let results = unsafe { stmt.results(metadata)? };
7676
results.map(|mut row| {
7777
U::Row::build_from_row(&mut row)
@@ -118,11 +118,9 @@ impl MysqlConnection {
118118
{
119119
let mut stmt = self.statement_cache
120120
.cached_statement(source, &[], |sql| self.raw_connection.prepare(sql))?;
121-
let mut bind_collector = RawBytesBindCollector::<Mysql>::new();
121+
let mut bind_collector = MysqlBindCollector::new();
122122
try!(source.collect_binds(&mut bind_collector, &()));
123-
let metadata = bind_collector.metadata;
124-
let binds = bind_collector.binds;
125-
try!(stmt.bind(metadata.into_iter().zip(binds)));
123+
try!(stmt.bind(bind_collector.binds));
126124
Ok(stmt)
127125
}
128126

diesel/src/mysql/connection/stmt/iterator.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::{ffi, libc, Binds, Statement, StatementMetadata};
44
use mysql::{Mysql, MysqlType};
55
use result::QueryResult;
66
use row::*;
7+
use sql_types::IsSigned;
78

89
pub struct StatementIterator<'a> {
910
stmt: &'a mut Statement,
@@ -12,7 +13,7 @@ pub struct StatementIterator<'a> {
1213

1314
#[cfg_attr(feature = "clippy", allow(should_implement_trait))] // don't neet `Iterator` here
1415
impl<'a> StatementIterator<'a> {
15-
pub fn new(stmt: &'a mut Statement, types: Vec<MysqlType>) -> QueryResult<Self> {
16+
pub fn new(stmt: &'a mut Statement, types: Vec<(MysqlType, IsSigned)>) -> QueryResult<Self> {
1617
let mut output_binds = Binds::from_output_types(types);
1718

1819
execute_statement(stmt, &mut output_binds)?;

diesel/src/mysql/connection/stmt/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use self::metadata::*;
1111
use super::bind::Binds;
1212
use mysql::MysqlType;
1313
use result::{DatabaseErrorKind, QueryResult};
14+
use sql_types::IsSigned;
1415
use util::NonNull;
1516

1617
pub struct Statement {
@@ -39,7 +40,7 @@ impl Statement {
3940

4041
pub fn bind<Iter>(&mut self, binds: Iter) -> QueryResult<()>
4142
where
42-
Iter: IntoIterator<Item = (MysqlType, Option<Vec<u8>>)>,
43+
Iter: IntoIterator<Item = (MysqlType, IsSigned, Option<Vec<u8>>)>,
4344
{
4445
let mut input_binds = Binds::from_input_data(binds);
4546
input_binds.with_mysql_binds(|bind_ptr| {
@@ -72,7 +73,10 @@ impl Statement {
7273
/// This function should be called instead of `execute` for queries which
7374
/// have a return value. After calling this function, `execute` can never
7475
/// be called on this statement.
75-
pub unsafe fn results(&mut self, types: Vec<MysqlType>) -> QueryResult<StatementIterator> {
76+
pub unsafe fn results(
77+
&mut self,
78+
types: Vec<(MysqlType, IsSigned)>,
79+
) -> QueryResult<StatementIterator> {
7680
StatementIterator::new(self, types)
7781
}
7882

diesel/src/mysql/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! MySQL, you may need to work with this module directly.
66
77
mod backend;
8+
mod bind_collector;
89
mod connection;
910

1011
mod query_builder;

diesel/src/mysql/types/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ where
8888
fn metadata(lookup: &()) -> MysqlType {
8989
<Mysql as HasSqlType<ST>>::metadata(lookup)
9090
}
91+
92+
fn is_signed() -> IsSigned {
93+
IsSigned::Unsigned
94+
}
9195
}
9296

9397
/// Represents the MySQL datetime type.

diesel/src/sql_types/mod.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -401,17 +401,44 @@ pub trait HasSqlType<ST>: TypeMetadata {
401401
/// of this method should not do dynamic lookup unless absolutely necessary
402402
fn metadata(lookup: &Self::MetadataLookup) -> Self::TypeMetadata;
403403

404-
/// Fetch the metadata for a tuple representing an entire row
405-
///
406-
/// The default implementation of this method simply calls `Self::metadata`.
407-
/// You generally should not need to override this method.
408-
///
409-
/// However, if you are writing an implementation of `HasSqlType` that
410-
/// simply delegates to an inner type (for example, `Nullable` does this),
411-
/// then you should ensure that you delegate this method as well.
404+
#[doc(hidden)]
405+
#[cfg(feature = "with-deprecated")]
406+
#[deprecated(
407+
since = "1.4.0",
408+
note = "This method is no longer used, and has been deprecated without replacement"
409+
)]
412410
fn row_metadata(out: &mut Vec<Self::TypeMetadata>, lookup: &Self::MetadataLookup) {
413411
out.push(Self::metadata(lookup))
414412
}
413+
414+
#[doc(hidden)]
415+
#[cfg(feature = "mysql")]
416+
fn is_signed() -> IsSigned {
417+
IsSigned::Signed
418+
}
419+
420+
#[doc(hidden)]
421+
#[cfg(feature = "mysql")]
422+
fn mysql_metadata(lookup: &Self::MetadataLookup) -> (Self::TypeMetadata, IsSigned) {
423+
(Self::metadata(lookup), Self::is_signed())
424+
}
425+
426+
#[doc(hidden)]
427+
#[cfg(feature = "mysql")]
428+
fn mysql_row_metadata(
429+
out: &mut Vec<(Self::TypeMetadata, IsSigned)>,
430+
lookup: &Self::MetadataLookup,
431+
) {
432+
out.push(Self::mysql_metadata(lookup))
433+
}
434+
}
435+
436+
#[doc(hidden)]
437+
#[cfg(feature = "mysql")]
438+
#[derive(Debug, Clone, Copy)]
439+
pub enum IsSigned {
440+
Signed,
441+
Unsigned,
415442
}
416443

417444
/// Information about how a backend stores metadata about given SQL types

diesel/src/type_impls/option.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ use row::NamedRow;
1010
use serialize::{self, IsNull, Output, ToSql};
1111
use sql_types::{HasSqlType, NotNull, Nullable};
1212

13+
#[cfg(feature = "mysql")]
14+
use sql_types::IsSigned;
15+
1316
impl<T, DB> HasSqlType<Nullable<T>> for DB
1417
where
1518
DB: Backend + HasSqlType<T>,
@@ -19,9 +22,19 @@ where
1922
<DB as HasSqlType<T>>::metadata(lookup)
2023
}
2124

25+
#[cfg(feature = "with-deprecated")]
26+
#[allow(deprecated)]
2227
fn row_metadata(out: &mut Vec<DB::TypeMetadata>, lookup: &DB::MetadataLookup) {
2328
<DB as HasSqlType<T>>::row_metadata(out, lookup)
2429
}
30+
31+
#[cfg(feature = "mysql")]
32+
fn mysql_row_metadata(
33+
out: &mut Vec<(DB::TypeMetadata, IsSigned)>,
34+
lookup: &DB::MetadataLookup,
35+
) {
36+
<DB as HasSqlType<T>>::mysql_row_metadata(out, lookup)
37+
}
2538
}
2639

2740
impl<T> QueryId for Nullable<T>

0 commit comments

Comments
 (0)