1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use crate::{fbig::FBig, repr::Repr, round::Round};
use _bytes::BufMut;
use dashu_base::{ConversionError, Sign};
use postgres_types_v02::{private::BytesMut, to_sql_checked, FromSql, IsNull, ToSql, Type};
use std::error;

use super::Numeric;

impl<'a> FromSql<'a> for Numeric {
    #[inline]
    fn accepts(ty: &Type) -> bool {
        *ty == Type::NUMERIC
    }

    fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn error::Error + Sync + Send>> {
        assert!(raw.len() >= 8);
        assert!(*ty == Type::NUMERIC);

        #[inline(always)]
        fn read16(bytes: &[u8]) -> u16 {
            u16::from_be_bytes(bytes.try_into().unwrap())
        }

        // number of digits (in base NBASE = 10000)
        let num_digits = read16(&raw[..2]) as usize;

        // exponent (in base NBASE = 10000)
        let weight = read16(&raw[2..4]) as i16;

        // sign flags
        let sign = match read16(&raw[4..6]) {
            0x0000 => Sign::Positive,
            0x4000 => Sign::Negative,
            0xC000 => return Err(ConversionError::OutOfBounds.into()), // nan
            0xD000 => return Ok(Numeric::infinity()),
            0xF000 => return Ok(Numeric::neg_infinity()),
            _ => panic!(),
        };

        // precision (in base 10 digits)
        let scale = read16(&raw[6..8]);

        // parse the digits through the iterator
        Ok(Self {
            sign,
            is_inf: false,
            weight,
            dscale: scale,
            digits: raw[8..]
                .chunks(2)
                .take(num_digits)
                .map(|bytes| read16(bytes) as i16)
                .collect(),
        })
    }
}

impl<'a, R: Round> FromSql<'a> for FBig<R, 10> {
    #[inline]
    fn accepts(ty: &Type) -> bool {
        <Numeric as FromSql>::accepts(ty)
    }

    #[inline]
    fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn error::Error + Sync + Send>> {
        Ok(<Numeric as FromSql>::from_sql(ty, raw)?.into())
    }
}

impl<'a> FromSql<'a> for Repr<10> {
    #[inline]
    fn accepts(ty: &Type) -> bool {
        <Numeric as FromSql>::accepts(ty)
    }

    #[inline]
    fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn error::Error + Sync + Send>> {
        Ok(<Numeric as FromSql>::from_sql(ty, raw)?.into())
    }
}

impl ToSql for Numeric {
    #[inline]
    fn accepts(ty: &Type) -> bool {
        *ty == Type::NUMERIC
    }

    fn to_sql(
        &self,
        ty: &Type,
        out: &mut BytesMut,
    ) -> Result<IsNull, Box<dyn error::Error + Sync + Send>> {
        assert!(*ty == Type::NUMERIC);

        let num_digits = self.digits.len();

        // reserve bytes
        out.reserve(8 + num_digits * 2);

        // put headers
        out.put_u16(num_digits.try_into().unwrap());
        out.put_i16(self.weight);
        out.put_u16(match (self.sign, self.is_inf) {
            (Sign::Positive, false) => 0x0000,
            (Sign::Negative, false) => 0x4000,
            (Sign::Positive, true) => 0xD000,
            (Sign::Negative, true) => 0xF000,
        });
        out.put_u16(self.dscale);

        // put the digits
        for digit in self.digits[0..num_digits].iter() {
            out.put_i16(*digit);
        }

        Ok(IsNull::No)
    }

    to_sql_checked!();
}

impl ToSql for Repr<10> {
    #[inline]
    fn accepts(ty: &Type) -> bool {
        <Numeric as ToSql>::accepts(ty)
    }

    #[inline]
    fn to_sql(
        &self,
        ty: &Type,
        out: &mut BytesMut,
    ) -> Result<IsNull, Box<dyn error::Error + Sync + Send>> {
        let num: Numeric = self.try_into()?;
        num.to_sql(ty, out)
    }

    to_sql_checked!();
}

impl<R: Round> ToSql for FBig<R, 10> {
    #[inline]
    fn accepts(ty: &Type) -> bool {
        <Numeric as ToSql>::accepts(ty)
    }

    #[inline]
    fn to_sql(
        &self,
        ty: &Type,
        out: &mut BytesMut,
    ) -> Result<IsNull, Box<dyn error::Error + Sync + Send>> {
        let num: Numeric = self.try_into()?;
        num.to_sql(ty, out)
    }

    to_sql_checked!();
}