Skip to main content

libsqlite3_sys/
decimal.rs

1//! Decimal envelopes and arithmetic linked into the patched SQLite engine.
2use crate as ffi;
3use bigdecimal::{BigDecimal, RoundingMode};
4use num_bigint::BigInt;
5use num_integer::Integer;
6use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero};
7use std::{ffi::c_char, str::FromStr};
8
9#[derive(Debug)]
10pub struct Error(pub String);
11pub type Result<T> = std::result::Result<T, Error>;
12
13pub const PREFIX: &[u8] = b"d";
14pub fn parse(text: &str) -> Result<BigDecimal> {
15    if text.len() > 8194 {
16        return Err(Error("DECIMAL exceeds size limit".into()));
17    }
18    let number = BigDecimal::from_str(text)
19        .map_err(|_| Error("invalid DECIMAL".into()))?
20        .normalized();
21    let (coefficient, scale) = number.as_bigint_and_exponent();
22    if coefficient.abs().to_string().len() > 4096 || scale.unsigned_abs() > 4096 {
23        return Err(Error("DECIMAL exceeds 4096-digit limit".into()));
24    }
25    Ok(number)
26}
27
28pub fn encode(text: &str) -> Result<Vec<u8>> {
29    let text = parse(text)?.normalized().to_plain_string();
30    let mut bytes = PREFIX.to_vec();
31    bytes.extend_from_slice(text.as_bytes());
32    Ok(bytes)
33}
34
35pub fn decode(bytes: &[u8]) -> Option<&str> {
36    let text = std::str::from_utf8(bytes.strip_prefix(PREFIX)?).ok()?;
37    let unsigned = text.strip_prefix(['-', '+']).unwrap_or(text);
38    if unsigned.bytes().filter(|byte| *byte == b'.').count() > 1
39        || !unsigned
40            .bytes()
41            .all(|byte| byte.is_ascii_digit() || byte == b'.')
42    {
43        return None;
44    }
45    parse(text).ok()?;
46    Some(text)
47}
48
49fn exact_divide(left: BigDecimal, right: BigDecimal) -> Result<BigDecimal> {
50    let (mut numerator, left_scale) = left.as_bigint_and_exponent();
51    let (mut denominator, right_scale) = right.as_bigint_and_exponent();
52    if denominator.is_zero() {
53        return Err(Error("DECIMAL division by zero".into()));
54    }
55    let gcd = numerator.gcd(&denominator);
56    numerator /= &gcd;
57    denominator /= gcd;
58    if denominator.is_negative() {
59        numerator = -numerator;
60        denominator = -denominator;
61    }
62    let mut powers = [0u32; 2];
63    for (index, factor) in [2, 5].into_iter().enumerate() {
64        let factor = BigInt::from(factor);
65        while (&denominator % &factor).is_zero() {
66            denominator /= &factor;
67            powers[index] += 1;
68        }
69    }
70    if !denominator.is_one() {
71        return Err(Error("nonterminating DECIMAL division; use decimal_div with an explicit scale and rounding mode".into()));
72    }
73    let scale = powers[0].max(powers[1]);
74    numerator *= BigInt::from(2).pow(scale - powers[0]);
75    numerator *= BigInt::from(5).pow(scale - powers[1]);
76    Ok(BigDecimal::new(
77        numerator,
78        left_scale - right_scale + i64::from(scale),
79    ))
80}
81
82unsafe fn operand(kind: i32, data: *const c_char, length: i32) -> Result<BigDecimal> {
83    // SAFETY: SQLite passes a readable buffer for the duration of the callback.
84    let bytes = unsafe { std::slice::from_raw_parts(data.cast::<u8>(), length as usize) };
85    if kind == 3 {
86        let raw: [u8; 8] = bytes.try_into().map_err(|_| Error("invalid REAL".into()))?;
87        return BigDecimal::from_f64(f64::from_ne_bytes(raw))
88            .ok_or_else(|| Error("non-finite REAL cannot be DECIMAL".into()));
89    }
90    parse(std::str::from_utf8(bytes).map_err(|_| Error("invalid DECIMAL encoding".into()))?)
91}
92
93pub(crate) unsafe fn output(text: &[u8], result: *mut *mut c_char, length: *mut i32) -> i32 {
94    // SAFETY: the caller owns both output slots; SQLite owns the allocation.
95    let buffer = unsafe { ffi::sqlite3_malloc64((text.len() + 1) as u64) }.cast::<c_char>();
96    if buffer.is_null() {
97        return ffi::SQLITE_NOMEM;
98    }
99    unsafe {
100        std::ptr::copy_nonoverlapping(text.as_ptr(), buffer.cast(), text.len());
101        *buffer.add(text.len()) = 0;
102        *result = buffer;
103        *length = text.len() as i32;
104    }
105    ffi::SQLITE_OK
106}
107
108#[unsafe(no_mangle)]
109unsafe extern "C" fn boxdb_decimal_eval(
110    operation: i32,
111    kind_left: i32,
112    left: *const c_char,
113    left_len: i32,
114    kind_right: i32,
115    right: *const c_char,
116    right_len: i32,
117    result: *mut *mut c_char,
118    result_len: *mut i32,
119) -> i32 {
120    let value = (|| -> Result<Vec<u8>> {
121        if operation != 8 && (kind_left == 3 || (operation != 0 && kind_right == 3)) {
122            return Err(Error(
123                "DECIMAL arithmetic requires an explicit REAL conversion".into(),
124            ));
125        }
126        let left = unsafe { operand(kind_left, left, left_len)? };
127        let number = if operation == 0 || operation == 8 {
128            left
129        } else {
130            let right = unsafe { operand(kind_right, right, right_len)? };
131            match operation {
132                1 => left + right,
133                2 => left - right,
134                3 => left * right,
135                4 => exact_divide(left, right)?,
136                5 => {
137                    if right.is_zero() {
138                        return Err(Error("DECIMAL remainder by zero".into()));
139                    }
140                    left % right
141                }
142                6 => {
143                    let scale = right
144                        .to_i64()
145                        .filter(|scale| (0..=4096).contains(scale))
146                        .ok_or_else(|| Error("invalid DECIMAL rounding scale".into()))?;
147                    left.with_scale_round(scale, RoundingMode::HalfUp)
148                }
149                7 => left.abs(),
150                _ => return Err(Error("unsupported DECIMAL operator".into())),
151            }
152        };
153        let text = number.to_plain_string();
154        if operation == 6 {
155            parse(&text)?;
156            let mut bytes = PREFIX.to_vec();
157            bytes.extend_from_slice(text.as_bytes());
158            Ok(bytes)
159        } else {
160            encode(&text)
161        }
162    })();
163    match value {
164        Ok(bytes) => unsafe { output(&bytes, result, result_len) },
165        Err(error) => {
166            let rc = unsafe { output(error.0.as_bytes(), result, result_len) };
167            if rc == ffi::SQLITE_OK {
168                ffi::SQLITE_ERROR
169            } else {
170                rc
171            }
172        }
173    }
174}
175
176#[unsafe(no_mangle)]
177unsafe extern "C" fn boxdb_decimal_compare(
178    kind_left: i32,
179    left: *const c_char,
180    left_len: i32,
181    kind_right: i32,
182    right: *const c_char,
183    right_len: i32,
184) -> i32 {
185    let left = unsafe { operand(kind_left, left, left_len) };
186    let right = unsafe { operand(kind_right, right, right_len) };
187    match (left, right) {
188        (Ok(left), Ok(right)) => left.cmp(&right) as i32,
189        _ => 0,
190    }
191}