libsqlite3_sys/
decimal.rs1use 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 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 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}