Skip to main content

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(mut bytes: &[u8]) -> AvroResult<BigDecimal> {
51    let mut big_decimal_buffer = match decode_len(&mut bytes) {
52        Ok(size) => vec![0u8; size],
53        Err(err) => return Err(Details::BigDecimalLen(Box::new(err)).into()),
54    };
55
56    bytes
57        .read_exact(&mut big_decimal_buffer[..])
58        .map_err(Details::ReadDouble)?;
59
60    match decode_long(&mut bytes) {
61        Ok(Value::Long(scale_value)) => {
62            let big_int: BigInt = BigInt::from_signed_bytes_be(&big_decimal_buffer);
63            let decimal = BigDecimal::new(big_int, scale_value);
64            Ok(decimal)
65        }
66        _ => Err(Details::BigDecimalScale.into()),
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use std::{
73        fs::File,
74        io::BufReader,
75        ops::{Div, Mul},
76        str::FromStr,
77    };
78
79    use apache_avro_test_helper::TestResult;
80    use bigdecimal::{One, Zero};
81    use pretty_assertions::assert_eq;
82
83    use super::*;
84    use crate::{
85        Codec, Reader, Schema, Writer, error::Error, reader::datum::GenericDatumReader,
86        types::Record, writer::datum::GenericDatumWriter,
87    };
88
89    #[test]
90    fn test_avro_3779_bigdecimal_serial() -> TestResult {
91        let value: BigDecimal =
92            bigdecimal::BigDecimal::from(-1421).div(bigdecimal::BigDecimal::from(2));
93        let mut current: BigDecimal = BigDecimal::one();
94
95        for iter in 1..180 {
96            let buffer: Vec<u8> = serialize_big_decimal(&current)?;
97
98            let mut as_slice = buffer.as_slice();
99            decode_long(&mut as_slice)?;
100
101            let mut result: Vec<u8> = Vec::new();
102            result.extend_from_slice(as_slice);
103
104            let deserialize_big_decimal: Result<BigDecimal, Error> =
105                deserialize_big_decimal(&result);
106            assert!(
107                deserialize_big_decimal.is_ok(),
108                "can't deserialize for iter {iter}"
109            );
110            assert_eq!(current, deserialize_big_decimal?, "not equals for {iter}");
111            current = current.mul(&value);
112        }
113
114        let buffer: Vec<u8> = serialize_big_decimal(&BigDecimal::zero())?;
115        let mut as_slice = buffer.as_slice();
116        decode_long(&mut as_slice)?;
117
118        let mut result: Vec<u8> = Vec::new();
119        result.extend_from_slice(as_slice);
120
121        let deserialize_big_decimal: Result<BigDecimal, Error> = deserialize_big_decimal(&result);
122        assert!(
123            deserialize_big_decimal.is_ok(),
124            "can't deserialize for zero"
125        );
126        assert_eq!(
127            BigDecimal::zero(),
128            deserialize_big_decimal?,
129            "not equals for zero"
130        );
131
132        Ok(())
133    }
134
135    #[test]
136    fn test_avro_3779_record_with_bg() -> TestResult {
137        let schema_str = r#"
138        {
139          "type": "record",
140          "name": "test",
141          "fields": [
142            {
143              "name": "field_name",
144              "type": {
145                "type": "bytes",
146                "logicalType": "big-decimal"
147              }
148            }
149          ]
150        }
151        "#;
152        let schema = Schema::parse_str(schema_str)?;
153
154        // build record with big decimal value
155        let mut record = Record::new(&schema).unwrap();
156        let val = BigDecimal::new(BigInt::from(12), 2);
157        record.put("field_name", val.clone());
158
159        // write a record
160        let codec = Codec::Null;
161        let mut writer = Writer::builder()
162            .schema(&schema)
163            .codec(codec)
164            .writer(Vec::new())
165            .build()?;
166
167        writer.append_value(record.clone())?;
168        writer.flush()?;
169
170        // read record
171        let wrote_data = writer.into_inner()?;
172        let mut reader = Reader::new(&wrote_data[..])?;
173
174        let value = reader.next().unwrap()?;
175
176        // extract field value
177        let big_decimal_value: &Value = match value {
178            Value::Record(ref fields) => Ok(&fields[0].1),
179            other => Err(format!("Expected a Value::Record, got: {other:?}")),
180        }?;
181
182        let x1res: &BigDecimal = match big_decimal_value {
183            Value::BigDecimal(s) => Ok(s),
184            other => Err(format!("Expected Value::BigDecimal, got: {other:?}")),
185        }?;
186        assert_eq!(&val, x1res);
187
188        Ok(())
189    }
190
191    #[test]
192    fn avro_rs_338_deserialize_serde_way() -> TestResult {
193        #[derive(Clone, PartialEq, Eq, Debug, Default, serde::Deserialize, serde::Serialize)]
194        struct Test {
195            big_decimal: BigDecimal,
196        }
197
198        let schema_str = r#"
199        {
200          "type": "record",
201          "name": "Test",
202          "fields": [
203            {
204              "name": "big_decimal",
205              "type": "string"
206            }
207          ]
208        }
209        "#;
210        let schema = Schema::parse_str(schema_str)?;
211
212        let test = Test::default();
213
214        let serialized = GenericDatumWriter::builder(&schema)
215            .build()?
216            .write_ser_to_vec(&test)?;
217        let value: Test = GenericDatumReader::builder(&schema)
218            .build()?
219            .read_deser(&mut &serialized[..])?;
220
221        assert_eq!(value, test);
222
223        Ok(())
224    }
225
226    #[test]
227    fn avro_rs_338_deserialize_serde_way_with_bigdecimal() -> TestResult {
228        #[derive(Clone, PartialEq, Eq, Debug, Default, serde::Deserialize, serde::Serialize)]
229        #[serde(rename = "test")]
230        struct Test {
231            #[serde(with = "crate::serde::bigdecimal")]
232            big_decimal: BigDecimal,
233        }
234
235        let schema_str = r#"
236        {
237          "type": "record",
238          "name": "test",
239          "fields": [
240            {
241              "name": "big_decimal",
242              "type": {
243                "type": "bytes",
244                "logicalType": "big-decimal"
245              }
246            }
247          ]
248        }
249        "#;
250        let schema = Schema::parse_str(schema_str)?;
251
252        let test = Test::default();
253
254        // write a record
255        let mut writer = Writer::new(&schema, Vec::new())?;
256        writer.append_ser(test.clone())?;
257
258        let wrote_data = writer.into_inner()?;
259
260        // read record
261        let mut reader = Reader::new(&wrote_data[..])?.into_deser_iter();
262
263        let value = reader.next().unwrap()?;
264
265        assert_eq!(test, value);
266
267        Ok(())
268    }
269
270    #[test]
271    fn test_avro_3779_from_java_file() -> TestResult {
272        // Open file generated with Java code to ensure compatibility
273        // with Java big decimal logical type.
274        let file: File = File::open("./tests/bigdec.avro")?;
275        let mut reader = Reader::new(BufReader::new(&file))?;
276        let next_element = reader.next();
277        assert!(next_element.is_some());
278        let value = next_element.unwrap()?;
279        let bg = match value {
280            Value::Record(ref fields) => Ok(&fields[0].1),
281            other => Err(format!("Expected a Value::Record, got: {other:?}")),
282        }?;
283        let value_big_decimal = match bg {
284            Value::BigDecimal(val) => Ok(val),
285            other => Err(format!("Expected a Value::BigDecimal, got: {other:?}")),
286        }?;
287
288        let ref_value = BigDecimal::from_str("2.24")?;
289        assert_eq!(&ref_value, value_big_decimal);
290
291        Ok(())
292    }
293}