Skip to content

Commit

Permalink
Add Decimal128 support (#118)
Browse files Browse the repository at this point in the history
* [#53] supports decimal128 and add necessary documents

* add documentation for decimal128 methods

* add serialize + deserialize for decimal128

* add decimal128 tests + into_i32/_u32 methods
  • Loading branch information
lrlna authored and zonyitoo committed Mar 14, 2019
1 parent 8d8e90c commit 68a8e07
Show file tree
Hide file tree
Showing 14 changed files with 409 additions and 33 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ hostname = "0.1"
hex = "0.3"
md5 = "0.3"
try_from = "0.2"
decimal = "2.0.4"

[dev-dependencies]
assert_matches = "1.2"
Expand Down
18 changes: 15 additions & 3 deletions src/bson.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use chrono::{DateTime, Timelike, Utc};
use hex;
use serde_json::Value;

use decimal128::Decimal128;
use oid;
use ordered::OrderedDocument;
use spec::{BinarySubtype, ElementType};
Expand Down Expand Up @@ -72,6 +73,8 @@ pub enum Bson {
UtcDatetime(DateTime<Utc>),
/// Symbol (Deprecated)
Symbol(String),
/// [128-bit decimal floating point](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst)
Decimal128(Decimal128),
}

/// Alias for `Vec<Bson>`.
Expand Down Expand Up @@ -111,6 +114,7 @@ impl Debug for Bson {
Bson::ObjectId(ref id) => write!(f, "ObjectId({:?})", id),
Bson::UtcDatetime(date_time) => write!(f, "UtcDatetime({:?})", date_time),
Bson::Symbol(ref sym) => write!(f, "Symbol({:?})", sym),
Bson::Decimal128(ref d) => write!(f, "Decimal128({:?})", d),
}
}
}
Expand Down Expand Up @@ -152,6 +156,7 @@ impl Display for Bson {
Bson::ObjectId(ref id) => write!(fmt, "ObjectId(\"{}\")", id),
Bson::UtcDatetime(date_time) => write!(fmt, "Date(\"{}\")", date_time),
Bson::Symbol(ref sym) => write!(fmt, "Symbol(\"{}\")", sym),
Bson::Decimal128(ref d) => write!(fmt, "Decimal128({})", d),
}
}
}
Expand Down Expand Up @@ -275,9 +280,7 @@ impl From<Value> for Bson {
Value::String(x) => x.into(),
Value::Bool(x) => x.into(),
Value::Array(x) => Bson::Array(x.into_iter().map(Bson::from).collect()),
Value::Object(x) => {
Bson::from_extended_document(x.into_iter().map(|(k, v)| (k, v.into())).collect())
}
Value::Object(x) => Bson::from_extended_document(x.into_iter().map(|(k, v)| (k, v.into())).collect()),
Value::Null => Bson::Null,
}
}
Expand Down Expand Up @@ -326,6 +329,7 @@ impl From<Bson> for Value {
}),
// FIXME: Don't know what is the best way to encode Symbol type
Bson::Symbol(v) => json!({ "$symbol": v }),
Bson::Decimal128(ref v) => json!({ "$numberDecimal": v.to_string() }),
}
}
}
Expand All @@ -350,6 +354,7 @@ impl Bson {
Bson::ObjectId(..) => ElementType::ObjectId,
Bson::UtcDatetime(..) => ElementType::UtcDatetime,
Bson::Symbol(..) => ElementType::Symbol,
Bson::Decimal128(..) => ElementType::Decimal128Bit,
}
}

Expand Down Expand Up @@ -429,6 +434,11 @@ impl Bson {
"$symbol": v.to_owned(),
}
}
Bson::Decimal128(ref v) => {
doc! {
"$numberDecimal" => (v.to_string())
}
}
_ => panic!("Attempted conversion of invalid data type: {}", self),
}
}
Expand Down Expand Up @@ -463,6 +473,8 @@ impl Bson {
return Bson::UtcDatetime(Utc.timestamp(long / 1000, ((long % 1000) * 1000000) as u32));
} else if let Ok(sym) = values.get_str("$symbol") {
return Bson::Symbol(sym.to_owned());
} else if let Ok(dec) = values.get_str("$numberDecimal") {
return Bson::Decimal128(dec.parse::<Decimal128>().unwrap());
}
}

Expand Down
252 changes: 252 additions & 0 deletions src/decimal128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
//! [BSON Decimal128](https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst) data type representation

use std::fmt;
use std::str::FromStr;

use decimal::d128;

/// Decimal128 type
#[derive(Clone, PartialEq, PartialOrd)]
pub struct Decimal128 {
inner: d128,
}

impl Decimal128 {
/// Construct a `Decimal128` from string.
///
/// For example:
///
/// * `NaN`
/// * `Infinity` or `Inf`
/// * `1.0`, `+37.0`, `0.73e-7`, `.5`
///
/// ```rust
/// use bson::decimal128::Decimal128;
///
/// let dec128 = Decimal128::from_str("1.05E+3");
/// ```
pub fn from_str(s: &str) -> Decimal128 {
Decimal128 { inner: s.parse::<d128>().expect("Invalid Decimal128 string"), }
}

/// Construct a `Decimal128` from a `i32` number.
///
/// ```rust
/// use bson::decimal128::Decimal128;
///
/// let num: i32 = 23;
/// let dec128 = Decimal128::from_i32(num);
/// ```
pub fn from_i32(d: i32) -> Decimal128 {
Decimal128 { inner: From::from(d) }
}

/// Construct a `Decimal128` from a `u32` number.
///
/// ```rust
/// use bson::decimal128::Decimal128;
///
/// let num: u32 = 78;
/// let dec128 = Decimal128::from_u32(num);
/// ```
pub fn from_u32(d: u32) -> Decimal128 {
Decimal128 { inner: From::from(d) }
}

/// Construct a `Decimal128` from a `i32` number.
///
/// ```rust
/// use bson::decimal128::Decimal128;
///
/// let num: i32 = 23;
/// let dec128 = Decimal128::from_i32(num);
/// let int = dec128.into_i32();
/// assert_eq!(int, num);
/// ```
pub fn into_i32(&self) -> i32 {
Into::into(self.inner)
}

/// Construct a `Decimal128` from a `i32` number.
///
/// ```rust
/// use bson::decimal128::Decimal128;
///
/// let num: u32 = 23;
/// let dec128 = Decimal128::from_u32(num);
/// let int = dec128.into_u32();
/// assert_eq!(int, num);
/// ```
pub fn into_u32(&self) -> u32 {
Into::into(self.inner)
}

/// Create a new Decimal128 as `0`.
///
/// ```rust
/// use bson::decimal128::Decimal128;
///
/// let dec128 = Decimal128::zero();
/// ```
pub fn zero() -> Decimal128 {
Decimal128 { inner: d128::zero() }
}

#[doc(hidden)]
pub unsafe fn from_raw_bytes_le(mut raw: [u8; 16]) -> Decimal128 {
if cfg!(target_endian = "big") {
raw.reverse();
}

Decimal128 { inner: d128::from_raw_bytes(raw), }
}

#[doc(hidden)]
pub fn to_raw_bytes_le(&self) -> [u8; 16] {
let mut buf = self.inner.to_raw_bytes();
if cfg!(target_endian = "big") {
buf.reverse();
}
buf
}

/// Check if value is `NaN`
///
/// ```rust
/// use bson::decimal128::Decimal128;
///
/// let num: u32 = 78;
/// let dec128 = Decimal128::from_u32(num);
/// assert!(!dec128.is_nan());
/// ```
pub fn is_nan(&self) -> bool {
self.inner.is_nan()
}

/// Check if value is 0
///
/// ```rust
/// use bson::decimal128::Decimal128;
///
/// let num: u32 = 0;
/// let dec128 = Decimal128::from_u32(num);
/// assert!(dec128.is_zero());
/// ```
pub fn is_zero(&self) -> bool {
self.inner.is_zero()
}
}

impl fmt::Debug for Decimal128 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Decimal(\"{:?}\")", self.inner)
}
}

impl fmt::Display for Decimal128 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.inner)
}
}

impl fmt::LowerHex for Decimal128 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<d128 as fmt::LowerHex>::fmt(&self.inner, f)
}
}

impl fmt::LowerExp for Decimal128 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<d128 as fmt::LowerExp>::fmt(&self.inner, f)
}
}

impl FromStr for Decimal128 {
type Err = ();
fn from_str(s: &str) -> Result<Decimal128, ()> {
Ok(Decimal128::from_str(s))
}
}

impl Into<d128> for Decimal128 {
fn into(self) -> d128 {
self.inner
}
}

impl From<d128> for Decimal128 {
fn from(d: d128) -> Decimal128 {
Decimal128 { inner: d }
}
}

impl Default for Decimal128 {
fn default() -> Decimal128 {
Decimal128::zero()
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn decimal128_string() {
assert!(Decimal128::from_str("0").is_zero());
assert!(!Decimal128::from_str("12").is_nan());
assert!(!Decimal128::from_str("-76").is_nan());
assert!(!Decimal128::from_str("12.70").is_nan());
assert!(!Decimal128::from_str("+0.003").is_nan());
assert!(!Decimal128::from_str("017.").is_nan());
assert!(!Decimal128::from_str(".5").is_nan());
assert!(!Decimal128::from_str("4E+9").is_nan());
assert!(!Decimal128::from_str("0.73e-7").is_nan());
assert!(!Decimal128::from_str("Inf").is_nan());
assert!(!Decimal128::from_str("-infinity").is_nan());
assert!(Decimal128::from_str("NaN").is_nan());
}

#[test]
fn decimal128_i32() {
let num: i32 = 89;
let dec128 = Decimal128::from_i32(num);

assert!(!dec128.is_nan());
assert!(!dec128.is_zero());
assert_eq!(dec128.into_i32(), num);
}

#[test]
fn decimal128_u32() {
let num: u32 = 89;
let dec128 = Decimal128::from_u32(num);

assert!(!dec128.is_nan());
assert!(!dec128.is_zero());
assert_eq!(dec128.into_u32(), num);
}

#[test]
fn decimal128_0() {
let dec128 = Decimal128::zero();
assert!(dec128.is_zero());
}

#[test]
fn decimal128_is_zero() {
let dec128 = Decimal128::from_i32(234);
assert!(!dec128.is_zero());

let dec128_0 = Decimal128::from_i32(0);
assert!(dec128_0.is_zero());
}

#[test]
fn decimal128_is_nan() {
let dec128 = Decimal128::from_str("NaN");
assert!(dec128.is_nan());

let dec128 = Decimal128::from_i32(234);
assert!(!dec128.is_nan());
}
}
11 changes: 11 additions & 0 deletions src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ pub use self::error::{DecoderError, DecoderResult};
pub use self::serde::Decoder;

use std::io::Read;
use std::mem;

use byteorder::{LittleEndian, ReadBytesExt};
use chrono::offset::{LocalResult, TimeZone};
use chrono::Utc;
use decimal128::Decimal128;

use bson::{Array, Bson, Document};
use oid;
Expand Down Expand Up @@ -87,6 +89,14 @@ fn read_i64<R: Read + ?Sized>(reader: &mut R) -> DecoderResult<i64> {
reader.read_i64::<LittleEndian>().map_err(From::from)
}

#[inline]
fn read_f128<R: Read + ?Sized>(reader: &mut R) -> DecoderResult<Decimal128> {
let mut local_buf: [u8; 16] = unsafe { mem::uninitialized() };
try!(reader.read_exact(&mut local_buf));
let val = unsafe { Decimal128::from_raw_bytes_le(local_buf) };
Ok(val)
}

/// Attempt to decode a `Document` from a byte stream.
pub fn decode_document<R: Read + ?Sized>(reader: &mut R) -> DecoderResult<Document> {
let mut doc = Document::new();
Expand Down Expand Up @@ -222,6 +232,7 @@ fn decode_bson<R: Read + ?Sized>(reader: &mut R, tag: u8, utf8_lossy: bool) -> D
}
}
Some(Symbol) => read_string(reader, utf8_lossy).map(Bson::Symbol),
Some(Decimal128Bit) => read_f128(reader).map(Bson::Decimal128),
Some(Undefined) | Some(DbPointer) | Some(MaxKey) | Some(MinKey) | None => {
Err(DecoderError::UnrecognizedElementType(tag))
}
Expand Down
Loading

0 comments on commit 68a8e07

Please sign in to comment.