Skip to content

Commit 9dfbf42

Browse files
committed
enable arbitrary precision in the internal representation of values
- Enabled arbitrary precision for `serde_json` to handle large numbers without rounding. - Refactored decimal handling in `sql_to_json.rs` to utilize a new function for converting decimals to JSON format. Fixes #1052
1 parent 534fa42 commit 9dfbf42

File tree

5 files changed

+28
-16
lines changed

5 files changed

+28
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- See the [function documentation](https://sql-page.com/functions.sql?function=hmac) for detailed examples
2121
- Fixed a slight spacing issue in the list components empty value display.
2222
- Improved performance of setting a variable to a literal value. `SET x = 'hello'` is now executed locally by SQLPage and does not send anything to the database. This completely removes the cost of extracting static values into variables for cleaner SQL files.
23+
- Enable arbitrary precision in the internal representation of numbers. This guarantees zero precision loss when the database returns very large or very small DECIMAL or NUMERIC values.
2324

2425
## v0.37.1
2526
- fixed decoding of UUID values

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ tokio = { version = "1.24.1", features = ["macros", "rt", "process", "sync"] }
4444
tokio-stream = "0.1.9"
4545
anyhow = "1"
4646
serde = "1"
47-
serde_json = { version = "1.0.82", features = ["preserve_order", "raw_value"] }
47+
serde_json = { version = "1.0.82", features = [
48+
"preserve_order",
49+
"raw_value",
50+
"arbitrary_precision",
51+
] }
4852
lambda-web = { version = "0.2.1", features = ["actix4"], optional = true }
4953
sqlparser = { version = "0.59.0", default-features = false, features = [
5054
"std",
@@ -53,7 +57,7 @@ sqlparser = { version = "0.59.0", default-features = false, features = [
5357
async-stream = "0.3"
5458
async-trait = "0.1.61"
5559
async-recursion = "1.0.0"
56-
bigdecimal = "0.4.8"
60+
bigdecimal = { version = "0.4.8", features = ["serde-json"] }
5761
include_dir = "0.7.2"
5862
config = { version = "0.15.4", features = ["json"] }
5963
markdown = { version = "1.0.0-alpha.23", features = ["log"] }
@@ -85,7 +89,7 @@ lambda-web = ["dep:lambda-web", "odbc-static"]
8589

8690

8791
[patch.crates-io]
88-
odbc-sys = { git = "https://github.com/sqlpage/odbc-sys", branch="no-autotools" }
92+
odbc-sys = { git = "https://github.com/sqlpage/odbc-sys", branch = "no-autotools" }
8993

9094
[build-dependencies]
9195
awc = { version = "3", features = ["rustls-0_23-webpki-roots"] }

src/webserver/database/sql_to_json.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::utils::add_value_to_map;
22
use crate::webserver::database::blob_to_data_url;
3-
use bigdecimal::{BigDecimal, ToPrimitive};
3+
use bigdecimal::BigDecimal;
44
use chrono::{DateTime, FixedOffset, NaiveDateTime};
55
use serde_json::{self, Map, Value};
66
use sqlx::any::{AnyColumn, AnyRow, AnyTypeInfo, AnyTypeInfoKind};
@@ -68,6 +68,13 @@ fn decode_raw<'a, T: Decode<'a, sqlx::any::Any> + Default>(
6868
}
6969
}
7070

71+
fn decimal_to_json(decimal: &BigDecimal) -> Value {
72+
// to_plain_string always returns a valid JSON string
73+
Value::Number(serde_json::Number::from_string_unchecked(
74+
decimal.normalized().to_plain_string(),
75+
))
76+
}
77+
7178
pub fn sql_nonnull_to_json<'r>(mut get_ref: impl FnMut() -> sqlx::any::AnyValueRef<'r>) -> Value {
7279
use AnyTypeInfoKind::{Mssql, MySql};
7380
let raw_value = get_ref();
@@ -77,18 +84,7 @@ pub fn sql_nonnull_to_json<'r>(mut get_ref: impl FnMut() -> sqlx::any::AnyValueR
7784
let AnyTypeInfo(ref db_type) = *type_info;
7885
match type_name {
7986
"REAL" | "FLOAT" | "FLOAT4" | "FLOAT8" | "DOUBLE" => decode_raw::<f64>(raw_value).into(),
80-
"NUMERIC" | "DECIMAL" => {
81-
let decimal = decode_raw::<BigDecimal>(raw_value);
82-
if decimal.is_integer() {
83-
if let Some(int) = decimal.to_i64() {
84-
return int.into();
85-
}
86-
}
87-
if let Some(float) = decimal.to_f64() {
88-
return float.into();
89-
}
90-
decimal.to_string().into()
91-
}
87+
"NUMERIC" | "DECIMAL" => decimal_to_json(&decode_raw(raw_value)),
9288
"INT8" | "BIGINT" | "SERIAL8" | "BIGSERIAL" | "IDENTITY" | "INT64" | "INTEGER8"
9389
| "BIGINT SIGNED" => decode_raw::<i64>(raw_value).into(),
9490
"INT" | "INT4" | "INTEGER" | "MEDIUMINT" | "YEAR" => decode_raw::<i32>(raw_value).into(),
@@ -209,6 +205,7 @@ mod tests {
209205
42::INT8 as big_int,
210206
42.25::FLOAT4 as float4,
211207
42.25::FLOAT8 as float8,
208+
123456789123456789123456789::NUMERIC as numeric,
212209
TRUE as boolean,
213210
'2024-03-14'::DATE as date,
214211
'13:14:15'::TIME as time,
@@ -237,6 +234,7 @@ mod tests {
237234
"big_int": 42,
238235
"float4": 42.25,
239236
"float8": 42.25,
237+
"numeric": 123456789123456789123456789_u128,
240238
"boolean": true,
241239
"date": "2024-03-14",
242240
"time": "13:14:15",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
SET test_stored_number = 123456789123456789123456789;
2+
select 'text' as component,
3+
case $test_stored_number
4+
when '123456789123456789123456789' then 'It works !'
5+
else 'It failed ! Expected 123456789123456789123456789 but got ' || COALESCE($test_stored_number, 'NULL')
6+
end
7+
AS contents;

0 commit comments

Comments
 (0)