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
//! v7.38 (read01) — an unconstrained NUMERIC result column (from VALUES / UNION)
//! keeps each value's own scale (PG renders `1.0` / `1.00`, not `1.00` / `1.00`),
//! while DISTINCT / aggregate-DISTINCT still collapse numerically-equal values.
//! Oracle: live PG 18.4.
use spg_engine::{Engine, QueryResult};
fn render(e: &mut Engine, sql: &str) -> Vec<String> {
match e.execute(sql).unwrap() {
QueryResult::Rows { rows, .. } => rows
.iter()
.map(|r| match &r.values[0] {
spg_storage::Value::Numeric { scaled, scale, .. } => {
if *scale == 0 {
scaled.to_string()
} else {
let neg = *scaled < 0;
let digits = scaled.unsigned_abs().to_string();
let digits = format!("{:0>width$}", digits, width = *scale as usize + 1);
let point = digits.len() - *scale as usize;
format!(
"{}{}.{}",
if neg { "-" } else { "" },
&digits[..point],
&digits[point..]
)
}
}
v => format!("{v:?}"),
})
.collect(),
_ => panic!("rows"),
}
}
#[test]
fn numeric_scale_preserved_but_dedup_by_value() {
let mut e = Engine::new();
// VALUES keeps each literal's own scale.
assert_eq!(
render(
&mut e,
"SELECT x FROM (VALUES (1.0),(1.00),(2.5)) v(x) ORDER BY x"
),
vec!["1.0", "1.00", "2.5"]
);
// int ∪ numeric: the int promotes at scale 0, the numeric keeps its scale.
assert_eq!(
render(
&mut e,
"SELECT x FROM (SELECT 1 AS x UNION ALL SELECT 1.5) t ORDER BY x"
),
vec!["1", "1.5"]
);
// DISTINCT / aggregate-DISTINCT still collapse by numeric value.
let cnt = |e: &mut Engine, sql: &str| match e.execute(sql).unwrap() {
QueryResult::Rows { rows, .. } => match rows[0].values[0] {
spg_storage::Value::BigInt(n) => n,
_ => panic!(),
},
_ => panic!(),
};
assert_eq!(
cnt(
&mut e,
"SELECT count(DISTINCT x) FROM (VALUES (1.0),(1.00),(2.5)) v(x)"
),
2
);
}