apache_avro/
bigdecimal.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::{
19    AvroResult,
20    decode::{decode_len, decode_long},
21    encode::{encode_bytes, encode_long},
22    error::Details,
23    types::Value,
24};
25pub use bigdecimal::BigDecimal;
26use num_bigint::BigInt;
27use std::io::Read;
28
29pub(crate) fn big_decimal_as_bytes(decimal: &BigDecimal) -> AvroResult<Vec<u8>> {
30    let mut buffer: Vec<u8> = Vec::new();
31    let (big_int, exponent): (BigInt, i64) = decimal.as_bigint_and_exponent();
32    let big_endian_value: Vec<u8> = big_int.to_signed_bytes_be();
33    encode_bytes(&big_endian_value, &mut buffer)?;
34    encode_long(exponent, &mut buffer)?;
35
36    Ok(buffer)
37}
38
39pub(crate) fn serialize_big_decimal(decimal: &BigDecimal) -> AvroResult<Vec<u8>> {
40    // encode big decimal, without global size
41    let buffer = big_decimal_as_bytes(decimal)?;
42
43    // encode global size and content
44    let mut final_buffer: Vec<u8> = Vec::new();
45    encode_bytes(&buffer, &mut final_buffer)?;
46
47    Ok(final_buffer)
48}
49
50pub(crate) fn deserialize_big_decimal(bytes: &Vec<u8>) -> AvroResult<BigDecimal> {
51    let mut bytes: &[u8] = bytes.as_slice();
52    let mut big_decimal_buffer = match decode_len(&mut bytes) {
53        Ok(size) => vec![0u8; size],
54        Err(err) => return Err(Details::BigDecimalLen(Box::new(err)).into()),
55    };
56
57    bytes
58        .read_exact(&mut big_decimal_buffer[..])
59        .map_err(Details::ReadDouble)?;
60
61    match decode_long(&mut bytes) {
62        Ok(Value::Long(scale_value)) => {
63            let big_int: BigInt = BigInt::from_signed_bytes_be(&big_decimal_buffer);
64            let decimal = BigDecimal::new(big_int, scale_value);
65            Ok(decimal)
66        }
67        _ => Err(Details::BigDecimalScale.into()),
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::{Codec, Reader, Schema, Writer, error::Error, types::Record};
75    use apache_avro_test_helper::TestResult;
76    use bigdecimal::{One, Zero};
77    use pretty_assertions::assert_eq;
78    use std::{
79        fs::File,
80        io::BufReader,
81        ops::{Div, Mul},
82        str::FromStr,
83    };
84
85    #[test]
86    fn test_avro_3779_bigdecimal_serial() -> TestResult {
87        let value: BigDecimal =
88            bigdecimal::BigDecimal::from(-1421).div(bigdecimal::BigDecimal::from(2));
89        let mut current: BigDecimal = BigDecimal::one();
90
91        for iter in 1..180 {
92            let buffer: Vec<u8> = serialize_big_decimal(&current)?;
93
94            let mut as_slice = buffer.as_slice();
95            decode_long(&mut as_slice)?;
96
97            let mut result: Vec<u8> = Vec::new();
98            result.extend_from_slice(as_slice);
99
100            let deserialize_big_decimal: Result<BigDecimal, Error> =
101                deserialize_big_decimal(&result);
102            assert!(
103                deserialize_big_decimal.is_ok(),
104                "can't deserialize for iter {iter}"
105            );
106            assert_eq!(current, deserialize_big_decimal?, "not equals for {iter}");
107            current = current.mul(&value);
108        }
109
110        let buffer: Vec<u8> = serialize_big_decimal(&BigDecimal::zero())?;
111        let mut as_slice = buffer.as_slice();
112        decode_long(&mut as_slice)?;
113
114        let mut result: Vec<u8> = Vec::new();
115        result.extend_from_slice(as_slice);
116
117        let deserialize_big_decimal: Result<BigDecimal, Error> = deserialize_big_decimal(&result);
118        assert!(
119            deserialize_big_decimal.is_ok(),
120            "can't deserialize for zero"
121        );
122        assert_eq!(
123            BigDecimal::zero(),
124            deserialize_big_decimal?,
125            "not equals for zero"
126        );
127
128        Ok(())
129    }
130
131    #[test]
132    fn test_avro_3779_record_with_bg() -> TestResult {
133        let schema_str = r#"
134        {
135          "type": "record",
136          "name": "test",
137          "fields": [
138            {
139              "name": "field_name",
140              "type": "bytes",
141              "logicalType": "big-decimal"
142            }
143          ]
144        }
145        "#;
146        let schema = Schema::parse_str(schema_str)?;
147
148        // build record with big decimal value
149        let mut record = Record::new(&schema).unwrap();
150        let val = BigDecimal::new(BigInt::from(12), 2);
151        record.put("field_name", val.clone());
152
153        // write a record
154        let codec = Codec::Null;
155        let mut writer = Writer::builder()
156            .schema(&schema)
157            .codec(codec)
158            .writer(Vec::new())
159            .build();
160
161        writer.append(record.clone())?;
162        writer.flush()?;
163
164        // read record
165        let wrote_data = writer.into_inner()?;
166        let mut reader = Reader::new(&wrote_data[..])?;
167
168        let value = reader.next().unwrap()?;
169
170        // extract field value
171        let big_decimal_value: &Value = match value {
172            Value::Record(ref fields) => Ok(&fields[0].1),
173            other => Err(format!("Expected a Value::Record, got: {other:?}")),
174        }?;
175
176        let x1res: &BigDecimal = match big_decimal_value {
177            Value::BigDecimal(s) => Ok(s),
178            other => Err(format!("Expected Value::BigDecimal, got: {other:?}")),
179        }?;
180        assert_eq!(&val, x1res);
181
182        Ok(())
183    }
184
185    #[test]
186    fn test_avro_3779_from_java_file() -> TestResult {
187        // Open file generated with Java code to ensure compatibility
188        // with Java big decimal logical type.
189        let file: File = File::open("./tests/bigdec.avro")?;
190        let mut reader = Reader::new(BufReader::new(&file))?;
191        let next_element = reader.next();
192        assert!(next_element.is_some());
193        let value = next_element.unwrap()?;
194        let bg = match value {
195            Value::Record(ref fields) => Ok(&fields[0].1),
196            other => Err(format!("Expected a Value::Record, got: {other:?}")),
197        }?;
198        let value_big_decimal = match bg {
199            Value::BigDecimal(val) => Ok(val),
200            other => Err(format!("Expected a Value::BigDecimal, got: {other:?}")),
201        }?;
202
203        let ref_value = BigDecimal::from_str("2.24")?;
204        assert_eq!(&ref_value, value_big_decimal);
205
206        Ok(())
207    }
208}