mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-29 04:04:56 +00:00
implement DECIMAL type support for mysql
This commit is contained in:
parent
52408f3cbf
commit
b354ed430d
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
141
sqlx-core/src/mysql/types/bigdecimal.rs
Normal file
141
sqlx-core/src/mysql/types/bigdecimal.rs
Normal 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(),
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user