implement DECIMAL type support for mysql

This commit is contained in:
xiaopengli89 2020-04-06 16:53:27 +08:00 committed by Ryan Leckey
parent 52408f3cbf
commit b354ed430d
5 changed files with 157 additions and 1 deletions

View File

@ -136,6 +136,8 @@ impl<'c> Row<'c> {
(len_size, len.unwrap_or_default())
}
TypeId::NEWDECIMAL => (0, 1 + buffer[index] as usize),
id => {
unimplemented!("encountered unknown field type id: {:?}", id);
}

View File

@ -31,7 +31,7 @@ impl TypeId {
// Numeric: FLOAT, DOUBLE
pub const FLOAT: TypeId = TypeId(4);
pub const DOUBLE: TypeId = TypeId(5);
// pub const NEWDECIMAL: TypeId = TypeId(246);
pub const NEWDECIMAL: TypeId = TypeId(246);
// Date/Time: DATE, TIME, DATETIME, TIMESTAMP
pub const DATE: TypeId = TypeId(10);

View File

@ -0,0 +1,141 @@
use bigdecimal::{BigDecimal, Signed};
use num_bigint::{BigInt, Sign};
use crate::decode::Decode;
use crate::encode::{Encode};
use crate::types::Type;
use crate::mysql::protocol::TypeId;
use crate::mysql::{MySql, MySqlValue, MySqlTypeInfo, MySqlData};
use crate::Error;
use crate::io::Buf;
const SIGN_NEG: u8 = 0x2D;
const SCALE_START: u8 = 0x2E;
impl Type<MySql> for BigDecimal {
fn type_info() -> MySqlTypeInfo {
MySqlTypeInfo::new(TypeId::NEWDECIMAL)
}
}
impl Encode<MySql> for BigDecimal {
fn encode(&self, buf: &mut Vec<u8>) {
let size = Encode::<MySql>::size_hint(self) - 1;
assert!(size <= u8::MAX as usize, "Too large size");
buf.push(size as u8);
if self.is_negative() {
buf.push(SIGN_NEG);
}
let (bi, scale) = self.as_bigint_and_exponent();
let (_, mut radix) = bi.to_radix_be(10);
let mut scale_index: Option<usize> = None;
if scale < 0 {
radix.append(&mut vec![0u8; -scale as usize]);
} else {
let scale = scale as usize;
if scale >= radix.len() {
let mut radix_temp = vec![0u8; scale - radix.len() + 1];
radix_temp.append(&mut radix);
radix = radix_temp;
scale_index = Some(0);
} else {
scale_index = Some(radix.len() - scale - 1);
}
}
for (i, data) in radix.iter().enumerate() {
buf.push(*data + 0x30);
if let Some(si) = scale_index {
if si == i {
buf.push(SCALE_START);
scale_index = None;
}
}
}
}
/// 15, -2 => 1500
/// 15, 1 => 1.5
/// 15, 2 => 0.15
/// 15, 3 => 0.015
fn size_hint(&self) -> usize {
let (bi, scale) = self.as_bigint_and_exponent();
let (_, radix) = bi.to_radix_be(10);
let mut s = radix.len();
if scale < 0 {
s = s + (-scale) as usize
} else if scale > 0 {
let scale = scale as usize;
if scale >= s {
s = scale + 1
}
s = s + 1;
}
if self.is_negative() {
s = s + 1;
}
s + 1
}
}
impl Decode<'_, MySql> for BigDecimal {
fn decode(value: MySqlValue) -> crate::Result<Self> {
match value.try_get()? {
MySqlData::Binary(mut binary) => {
let len = binary.get_u8()?;
let mut negative = false;
let mut scale: Option<i64> = None;
let mut v: Vec<u8> = Vec::with_capacity(len as usize);
loop {
if binary.len() < 1 {
break
}
let data = binary.get_u8()?;
match data {
SIGN_NEG => {
if !negative {
negative = true;
} else {
return Err(Error::Decode(format!("Unexpected byte: {:X?}", data).into()));
}
},
SCALE_START => {
if scale.is_none() {
scale = Some(0);
} else {
return Err(Error::Decode(format!("Unexpected byte: {:X?}", data).into()));
}
},
0x30..=0x39 => {
scale = scale.map(|s| s + 1);
v.push(data - 0x30);
},
_ => return Err(Error::Decode(format!("Unexpected byte: {:X?}", data).into())),
}
}
let r = BigInt::from_radix_be(
if negative { Sign::Minus } else { Sign::Plus },
v.as_slice(),
10,
).ok_or(Error::Decode("Can't convert to BigInt".into()))?;
Ok(BigDecimal::new(r, scale.unwrap_or(0)))
},
MySqlData::Text(_) => {
Err(Error::Decode(
"`BigDecimal` can only be decoded from the binary protocol".into(),
))
},
}
}
}

View File

@ -41,6 +41,13 @@
//! | `time::Date` | DATE |
//! | `time::Time` | TIME |
//!
//! ### [`bigdecimal`](https://crates.io/crates/bigdecimal)
//! Requires the `bigdecimal` Cargo feature flag.
//!
//! | Rust type | MySQL type(s) |
//! |---------------------------------------|------------------------------------------------------|
//! | `bigdecimal::BigDecimal` | DECIMAL |
//!
//! # Nullable
//!
//! In addition, `Option<T>` is supported where `T` implements `Type`. An `Option<T>` represents
@ -54,6 +61,9 @@ mod int;
mod str;
mod uint;
#[cfg(feature = "bigdecimal")]
mod bigdecimal;
#[cfg(feature = "chrono")]
mod chrono;

View File

@ -40,6 +40,9 @@ impl_database_ext! {
#[cfg(feature = "time")]
sqlx::types::time::OffsetDateTime,
#[cfg(feature = "bigdecimal")]
sqlx::types::BigDecimal,
},
ParamChecking::Weak,
feature-types: info => info.type_feature_gate(),