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, from_value, 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_value(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 avro_rs_338_deserialize_serde_way() -> TestResult {
187        #[derive(Clone, PartialEq, Eq, Debug, Default, serde::Deserialize, serde::Serialize)]
188        struct Test {
189            big_decimal: BigDecimal,
190        }
191
192        let schema_str = r#"
193        {
194          "type": "record",
195          "name": "test",
196          "fields": [
197            {
198              "name": "big_decimal",
199              "type": "bytes",
200              "logicalType": "big-decimal"
201            }
202          ]
203        }
204        "#;
205        let schema = Schema::parse_str(schema_str)?;
206
207        let test = Test::default();
208
209        // write a record
210        let mut writer = Writer::new(&schema, Vec::new())?;
211        writer.append_ser(test.clone())?;
212
213        let wrote_data = writer.into_inner()?;
214
215        // read record
216        let mut reader = Reader::new(&wrote_data[..])?;
217
218        let value = reader.next().unwrap()?;
219
220        assert_eq!(test, from_value::<Test>(&value)?);
221
222        Ok(())
223    }
224
225    #[test]
226    fn test_avro_3779_from_java_file() -> TestResult {
227        // Open file generated with Java code to ensure compatibility
228        // with Java big decimal logical type.
229        let file: File = File::open("./tests/bigdec.avro")?;
230        let mut reader = Reader::new(BufReader::new(&file))?;
231        let next_element = reader.next();
232        assert!(next_element.is_some());
233        let value = next_element.unwrap()?;
234        let bg = match value {
235            Value::Record(ref fields) => Ok(&fields[0].1),
236            other => Err(format!("Expected a Value::Record, got: {other:?}")),
237        }?;
238        let value_big_decimal = match bg {
239            Value::BigDecimal(val) => Ok(val),
240            other => Err(format!("Expected a Value::BigDecimal, got: {other:?}")),
241        }?;
242
243        let ref_value = BigDecimal::from_str("2.24")?;
244        assert_eq!(&ref_value, value_big_decimal);
245
246        Ok(())
247    }
248}