reifydb_value/value/container/
decimal_array.rs1use std::{borrow::Borrow, sync::Arc};
5
6use arrow_array::{Array, ArrayRef, Decimal128Array, Decimal256Array, PrimitiveArray};
7use arrow_buffer::{ScalarBuffer, i256};
8use arrow_schema::DataType;
9
10use crate::value::{
11 Value,
12 constraint::{precision::Precision, scale::Scale},
13 decimal::{Decimal, unscaled},
14};
15
16pub const DECIMAL128_MAX_PRECISION: u8 = 38;
17
18#[derive(Clone, Debug)]
19pub enum DecimalArray {
20 Decimal128(Decimal128Array),
21 Decimal256(Decimal256Array),
22}
23
24impl DecimalArray {
25 pub fn from_unscaled(precision: Precision, scale: Scale, values: impl IntoIterator<Item = i256>) -> Self {
26 let values = values.into_iter().inspect(|&value| {
27 assert!(
28 unscaled::digits(value) <= precision.value(),
29 "unscaled value {value} does not fit precision {precision}"
30 )
31 });
32 if precision.value() <= DECIMAL128_MAX_PRECISION {
33 let natives: Vec<i128> = values
34 .map(|value| value.to_i128().expect("a value within precision 38 fits i128"))
35 .collect();
36 DecimalArray::Decimal128(
37 PrimitiveArray::new(ScalarBuffer::from(natives), None)
38 .with_data_type(data_type(precision, scale)),
39 )
40 } else {
41 let natives: Vec<i256> = values.collect();
42 DecimalArray::Decimal256(
43 PrimitiveArray::new(ScalarBuffer::from(natives), None)
44 .with_data_type(data_type(precision, scale)),
45 )
46 }
47 }
48
49 pub fn view(&self) -> DecimalView<'_> {
50 DecimalView::from(self)
51 }
52
53 pub fn into_array(self) -> ArrayRef {
54 match self {
55 DecimalArray::Decimal128(array) => Arc::new(array),
56 DecimalArray::Decimal256(array) => Arc::new(array),
57 }
58 }
59
60 pub fn len(&self) -> usize {
61 self.view().len()
62 }
63
64 pub fn is_empty(&self) -> bool {
65 self.len() == 0
66 }
67
68 pub fn precision(&self) -> Precision {
69 self.view().precision()
70 }
71
72 pub fn scale(&self) -> Scale {
73 self.view().scale()
74 }
75
76 pub fn data_type(&self) -> &DataType {
77 self.view().data_type()
78 }
79
80 pub fn unscaled_at(&self, index: usize) -> Option<i256> {
81 self.view().unscaled_at(index)
82 }
83
84 pub fn unscaled_values(&self) -> Vec<i256> {
85 self.view().unscaled_values()
86 }
87}
88
89#[derive(Clone, Copy, Debug)]
90pub enum DecimalView<'a> {
91 Decimal128(&'a Decimal128Array),
92 Decimal256(&'a Decimal256Array),
93}
94
95impl<'a> From<&'a DecimalArray> for DecimalView<'a> {
96 fn from(array: &'a DecimalArray) -> Self {
97 match array {
98 DecimalArray::Decimal128(array) => DecimalView::Decimal128(array),
99 DecimalArray::Decimal256(array) => DecimalView::Decimal256(array),
100 }
101 }
102}
103
104impl<'a> From<&DecimalView<'a>> for DecimalView<'a> {
105 fn from(view: &DecimalView<'a>) -> Self {
106 *view
107 }
108}
109
110impl From<DecimalView<'_>> for DecimalArray {
111 fn from(view: DecimalView<'_>) -> Self {
112 match view {
113 DecimalView::Decimal128(array) => DecimalArray::Decimal128(array.clone()),
114 DecimalView::Decimal256(array) => DecimalArray::Decimal256(array.clone()),
115 }
116 }
117}
118
119impl<'a> DecimalView<'a> {
120 pub fn array(&self) -> &'a dyn Array {
121 match *self {
122 DecimalView::Decimal128(array) => array,
123 DecimalView::Decimal256(array) => array,
124 }
125 }
126
127 pub fn len(&self) -> usize {
128 self.array().len()
129 }
130
131 pub fn is_empty(&self) -> bool {
132 self.len() == 0
133 }
134
135 pub fn precision(&self) -> Precision {
136 Precision::new(match *self {
137 DecimalView::Decimal128(array) => array.precision(),
138 DecimalView::Decimal256(array) => array.precision(),
139 })
140 }
141
142 pub fn scale(&self) -> Scale {
143 let scale = match *self {
144 DecimalView::Decimal128(array) => array.scale(),
145 DecimalView::Decimal256(array) => array.scale(),
146 };
147 Scale::new(u8::try_from(scale).expect("decimal arrays are built with a non-negative scale"))
148 }
149
150 pub fn data_type(&self) -> &'a DataType {
151 self.array().data_type()
152 }
153
154 pub fn unscaled_at(&self, index: usize) -> Option<i256> {
155 match *self {
156 DecimalView::Decimal128(array) => {
157 array.values().get(index).map(|&value| i256::from_i128(value))
158 }
159 DecimalView::Decimal256(array) => array.values().get(index).copied(),
160 }
161 }
162
163 pub fn unscaled_values(&self) -> Vec<i256> {
164 match *self {
165 DecimalView::Decimal128(array) => {
166 array.values().iter().map(|&value| i256::from_i128(value)).collect()
167 }
168 DecimalView::Decimal256(array) => array.values().to_vec(),
169 }
170 }
171}
172
173impl PartialEq for DecimalArray {
174 fn eq(&self, other: &Self) -> bool {
175 self.len() == other.len()
176 && (0..self.len()).all(|index| decimal_at(self, index) == decimal_at(other, index))
177 }
178}
179
180pub fn data_type(precision: Precision, scale: Scale) -> DataType {
181 if precision.value() <= DECIMAL128_MAX_PRECISION {
182 DataType::Decimal128(precision.value(), scale.value() as i8)
183 } else {
184 DataType::Decimal256(precision.value(), scale.value() as i8)
185 }
186}
187
188pub fn decimal_array<B: Borrow<Decimal>>(
189 precision: Precision,
190 scale: Scale,
191 values: impl IntoIterator<Item = B>,
192) -> DecimalArray {
193 let unscaled = values.into_iter().map(|value| {
194 let value = value.borrow();
195 value.rescale(scale.value())
196 .unwrap_or_else(|| panic!("decimal {value} does not rescale exactly to scale {scale}"))
197 .unscaled()
198 });
199 DecimalArray::from_unscaled(precision, scale, unscaled)
200}
201
202pub fn decimal_at<'a>(array: impl Into<DecimalView<'a>>, index: usize) -> Option<Decimal> {
203 checked_decimal_at(array, index).expect("a decimal array holds only valid decimals")
204}
205
206pub fn checked_decimal_at<'a>(array: impl Into<DecimalView<'a>>, index: usize) -> Option<Option<Decimal>> {
207 let array = array.into();
208 let scale = array.scale().value();
209 match array.unscaled_at(index) {
210 Some(value) => Decimal::from_parts(value, scale).map(Some),
211 None => Some(None),
212 }
213}
214
215pub fn decimals<'a>(array: impl Into<DecimalView<'a>>) -> Vec<Decimal> {
216 let array = array.into();
217 (0..array.len()).filter_map(|index| decimal_at(array, index)).collect()
218}
219
220pub fn decimal_get_value<'a>(array: impl Into<DecimalView<'a>>, index: usize) -> Value {
221 decimal_at(array, index).map(Value::Decimal).unwrap_or_else(Value::none)
222}
223
224pub fn decimal_as_string<'a>(array: impl Into<DecimalView<'a>>, index: usize) -> String {
225 decimal_at(array, index).map(|value| value.to_string()).unwrap_or_else(|| "none".to_string())
226}
227
228#[cfg(test)]
229mod family {
230 use std::str::FromStr;
231
232 use super::*;
233
234 fn decimal(text: &str) -> Decimal {
235 Decimal::from_str(text).unwrap()
236 }
237
238 #[test]
239 fn width_switches_to_decimal256_past_precision_thirty_eight() {
240 let widest = decimal_array(Precision::MAX, Scale::new(10), [decimal("1.5")]);
242 assert_eq!(widest.data_type(), &DataType::Decimal256(76, 10));
243 assert_eq!(data_type(Precision::new(38), Scale::new(38)), DataType::Decimal128(38, 38));
244 assert_eq!(data_type(Precision::MAX, Scale::MAX), DataType::Decimal256(76, 76));
245 }
246
247 #[test]
248 fn precision_and_scale_are_read_back_from_the_data_type() {
249 let array = decimal_array(Precision::new(12), Scale::new(4), [decimal("-3.25")]);
251 assert_eq!(array.precision(), Precision::new(12));
252 assert_eq!(array.scale(), Scale::new(4));
253 assert_eq!(array.unscaled_at(0), Some(i256::from_i128(-32500)));
254 assert_eq!(decimal_as_string(&array, 0), "-3.2500");
255 }
256
257 #[test]
258 fn decimals_are_rescaled_exactly_to_the_array_scale() {
259 let array = decimal_array(
261 Precision::new(10),
262 Scale::new(2),
263 [decimal("1.5"), decimal("-0.25"), decimal("7")],
264 );
265 assert_eq!(array.unscaled_values(), [i256::from_i128(150), i256::from_i128(-25), i256::from_i128(700)]);
266 assert_eq!(decimals(&array), [decimal("1.50"), decimal("-0.25"), decimal("7.00")]);
267 assert_eq!(decimal_get_value(&array, 0).to_string(), "1.50");
268 }
269
270 #[test]
271 #[should_panic(expected = "does not rescale exactly")]
272 fn a_decimal_with_more_fraction_digits_than_the_scale_panics() {
273 decimal_array(Precision::new(10), Scale::new(2), [decimal("1.555")]);
275 }
276
277 #[test]
278 #[should_panic(expected = "does not fit precision")]
279 fn a_value_wider_than_the_precision_panics() {
280 decimal_array(Precision::new(2), Scale::new(1), [decimal("100.0")]);
282 }
283
284 #[test]
285 fn reads_past_the_end_are_none() {
286 let dec = decimal_array(Precision::new(5), Scale::new(1), [decimal("0.5")]);
288 assert_eq!(decimal_at(&dec, 1), None);
289 assert_eq!(decimal_get_value(&dec, 1), Value::none());
290 assert_eq!(decimal_as_string(&dec, 1), "none");
291 }
292
293 #[test]
294 fn equality_compares_values_not_widths() {
295 let narrow = decimal_array(Precision::new(10), Scale::new(1), [decimal("1.5"), decimal("-2")]);
297 let wide = decimal_array(Precision::new(60), Scale::new(3), [decimal("1.5"), decimal("-2")]);
298 assert!(narrow == wide);
299 let other = decimal_array(Precision::new(10), Scale::new(1), [decimal("1.5"), decimal("-2.1")]);
300 assert!(narrow != other);
301 let shorter = decimal_array(Precision::new(10), Scale::new(1), [decimal("1.5")]);
302 assert!(narrow != shorter);
303 }
304}