Skip to main content

fluidattacks_database_core_domain/cell/
value.rs

1use alloc::string::String;
2use alloc::vec::Vec;
3use core::fmt;
4
5use chrono::{DateTime, NaiveDate, Utc};
6
7/// Most digits a decimal holds.
8pub const MAX_PRECISION: u8 = 38;
9
10/// Most digits after the point a decimal holds.
11pub const MAX_SCALE: u8 = 37;
12
13#[must_use]
14pub const fn valid_decimal(precision: u8, scale: u8) -> bool {
15    precision >= 1 && precision <= MAX_PRECISION && scale <= MAX_SCALE && scale <= precision
16}
17
18const MAX_UNSCALED: u128 = 99_999_999_999_999_999_999_999_999_999_999_999_999;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum DecimalError {
22    TooManyDigits { precision: u8 },
23    ScaleTooLarge,
24}
25
26impl fmt::Display for DecimalError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            Self::TooManyDigits { precision } => {
30                write!(f, "decimal has more than {precision} digits")
31            }
32            Self::ScaleTooLarge => write!(f, "decimal scale is over {MAX_SCALE}"),
33        }
34    }
35}
36
37impl core::error::Error for DecimalError {}
38
39/// An exact decimal: `unscaled * 10^-scale`, up to 38 digits. The `i128`
40/// reaches the 38 digits common decimal crates do not.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Decimal {
43    unscaled: i128,
44    scale: u8,
45}
46
47impl Decimal {
48    pub const fn new(unscaled: i128, scale: u8) -> Result<Self, DecimalError> {
49        if unscaled.unsigned_abs() > MAX_UNSCALED {
50            Err(DecimalError::TooManyDigits {
51                precision: MAX_PRECISION,
52            })
53        } else if scale > MAX_SCALE {
54            Err(DecimalError::ScaleTooLarge)
55        } else {
56            Ok(Self { unscaled, scale })
57        }
58    }
59
60    #[must_use]
61    pub const fn unscaled(&self) -> i128 {
62        self.unscaled
63    }
64
65    #[must_use]
66    pub const fn scale(&self) -> u8 {
67        self.scale
68    }
69}
70
71/// One cell as a row hands it over: not validated against a schema.
72#[derive(Debug, Clone, PartialEq)]
73pub enum RawValue {
74    Null,
75    Text(String),
76    Integer(i64),
77    Decimal(Decimal),
78    Float(f64),
79    Boolean(bool),
80    Date(NaiveDate),
81    Timestamp(DateTime<Utc>),
82    Array(Vec<Self>),
83    Json(serde_json::Value),
84}
85
86#[cfg(test)]
87mod tests {
88    use super::{Decimal, DecimalError};
89
90    const MAX: i128 = 99_999_999_999_999_999_999_999_999_999_999_999_999;
91
92    #[test]
93    fn decimal_keeps_38_digits_either_sign() {
94        assert_eq!(Decimal::new(MAX, 10).map(|d| d.unscaled()), Ok(MAX));
95        assert_eq!(Decimal::new(-MAX, 0).map(|d| d.unscaled()), Ok(-MAX));
96    }
97
98    #[test]
99    fn decimal_rejects_39_digits() {
100        assert_eq!(
101            Decimal::new(100_000_000_000_000_000_000_000_000_000_000_000_000, 0),
102            Err(DecimalError::TooManyDigits { precision: 38 })
103        );
104        assert_eq!(
105            Decimal::new(-100_000_000_000_000_000_000_000_000_000_000_000_000, 0),
106            Err(DecimalError::TooManyDigits { precision: 38 })
107        );
108        assert_eq!(
109            Decimal::new(i128::MIN, 0),
110            Err(DecimalError::TooManyDigits { precision: 38 })
111        );
112    }
113
114    #[test]
115    fn decimal_rejects_scale_over_37() {
116        assert_eq!(Decimal::new(1, 37).map(|d| d.scale()), Ok(37));
117        assert_eq!(Decimal::new(1, 38), Err(DecimalError::ScaleTooLarge));
118    }
119}