use spg_engine::{Engine, QueryResult};
fn first(e: &mut Engine, sql: &str) -> spg_storage::Value<'static> {
let r = e
.execute(sql)
.unwrap_or_else(|err| panic!("{sql}: {err:?}"));
let QueryResult::Rows { rows, .. } = r else {
panic!("expected Rows");
};
rows[0].values[0].clone()
}
fn as_f64(v: &spg_storage::Value<'_>) -> f64 {
match v {
spg_storage::Value::Float(f) => *f,
other => panic!("expected Float, got {other:?}"),
}
}
fn approx(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
#[test]
fn ln_of_e_is_one() {
let mut e = Engine::new();
let v = first(&mut e, "SELECT ln(exp(1))");
assert!(approx(as_f64(&v), 1.0, 1e-9), "got {}", as_f64(&v));
}
fn text(e: &mut Engine, sql: &str) -> String {
match first(e, sql) {
spg_storage::Value::Text(s) => s.to_string(),
other => panic!("{sql}: expected Text, got {other:?}"),
}
}
#[test]
fn log10_of_100_is_2() {
let mut e = Engine::new();
assert_eq!(
text(&mut e, "SELECT (log10(100.0))::text"),
"2.0000000000000000"
);
assert_eq!(
text(&mut e, "SELECT (log(100.0))::text"),
"2.0000000000000000"
);
assert_eq!(
text(&mut e, "SELECT (log10(1000.0))::text"),
"3.0000000000000000"
);
assert_eq!(
text(&mut e, "SELECT (log10(0.001))::text"),
"-3.0000000000000000"
);
assert_eq!(
text(&mut e, "SELECT (log10(2.0))::text"),
"0.3010299956639812"
);
assert_eq!(
text(&mut e, "SELECT pg_typeof(log10(100.0))::text"),
"numeric"
);
assert_eq!(as_f64(&first(&mut e, "SELECT log10(100)")), 2.0);
assert_eq!(
text(&mut e, "SELECT pg_typeof(log(100))::text"),
"double precision"
);
}
#[test]
fn log_base_2_of_8_is_3() {
let mut e = Engine::new();
assert_eq!(
text(&mut e, "SELECT (log(2.0, 8.0))::text"),
"3.0000000000000000"
);
assert_eq!(
text(&mut e, "SELECT (log(2, 8))::text"),
"3.0000000000000000"
);
assert_eq!(
text(&mut e, "SELECT (log(2, 10))::text"),
"3.3219280948873623"
);
assert_eq!(text(&mut e, "SELECT pg_typeof(log(2, 8))::text"), "numeric");
assert!(e.execute("SELECT log(1, 5)").is_err());
assert!(e.execute("SELECT log(0, 5)").is_err());
assert!(e.execute("SELECT log(2, -3)").is_err());
assert!(e.execute("SELECT log(-1.0)").is_err());
}
#[test]
fn cbrt_of_27_is_3() {
let mut e = Engine::new();
assert_eq!(as_f64(&first(&mut e, "SELECT cbrt(27.0)")), 3.0);
assert_eq!(as_f64(&first(&mut e, "SELECT cbrt(-27.0)")), -3.0);
assert_eq!(as_f64(&first(&mut e, "SELECT cbrt(1000000.0)")), 100.0);
assert_eq!(as_f64(&first(&mut e, "SELECT cbrt(-64.0)")), -4.0);
assert_eq!(
as_f64(&first(&mut e, "SELECT cbrt(2.0)")),
1.259_921_049_894_873_2
);
}
#[test]
fn sqrt_matches_pg() {
let mut e = Engine::new();
let txt = |e: &mut Engine, sql: &str| match first(e, sql) {
spg_storage::Value::Text(s) => s.to_string(),
other => panic!("expected Text, got {other:?}"),
};
assert_eq!(txt(&mut e, "SELECT sqrt(16.0)::text"), "4.000000000000000");
assert_eq!(txt(&mut e, "SELECT sqrt(2.0)::text"), "1.414213562373095");
assert_eq!(
txt(&mut e, "SELECT sqrt(130.0)::text"),
"11.401754250991380"
);
assert_eq!(as_f64(&first(&mut e, "SELECT sqrt(16)")), 4.0);
assert_eq!(
as_f64(&first(&mut e, "SELECT sqrt(2.0::float8)")),
1.414_213_562_373_095_1
);
}
#[test]
fn pi_returns_pi() {
let mut e = Engine::new();
let v = first(&mut e, "SELECT pi()");
assert!(
approx(as_f64(&v), core::f64::consts::PI, 1e-15),
"got {}",
as_f64(&v)
);
}
#[test]
fn gcd_lcm_basic() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT (gcd(12, 18))::text"), "6");
assert_eq!(text(&mut e, "SELECT (gcd(0, 5))::text"), "5");
assert_eq!(text(&mut e, "SELECT (gcd(-12, 18))::text"), "6");
assert_eq!(text(&mut e, "SELECT (lcm(4, 6))::text"), "12");
assert_eq!(text(&mut e, "SELECT (lcm(0, 5))::text"), "0");
assert_eq!(
text(&mut e, "SELECT pg_typeof(gcd(12, 18))::text"),
"integer"
);
assert_eq!(text(&mut e, "SELECT pg_typeof(lcm(4, 6))::text"), "integer");
assert_eq!(
text(&mut e, "SELECT pg_typeof(gcd(12::bigint, 18))::text"),
"bigint"
);
}
#[test]
fn radians_degrees_roundtrip() {
let mut e = Engine::new();
let v = first(&mut e, "SELECT degrees(radians(90.0))");
assert!(approx(as_f64(&v), 90.0, 1e-9), "got {}", as_f64(&v));
}
#[test]
fn float8_overflow_and_underflow_error_like_pg() {
let mut e = Engine::new();
for sql in [
"SELECT 1e308::float8 * 10",
"SELECT 1e308::float8 + 1e308",
"SELECT (-1e308::float8) - 1e308",
"SELECT 1e308::float8 / 1e-10",
"SELECT 1e-300::float8 * 1e-300", ] {
assert!(
e.execute(sql).is_err(),
"{sql} should error (overflow/underflow)"
);
}
assert_eq!(as_f64(&first(&mut e, "SELECT 2.0::float8 * 3.0")), 6.0);
assert!(as_f64(&first(&mut e, "SELECT 'inf'::float8 * 2")).is_infinite());
assert_eq!(
as_f64(&first(&mut e, "SELECT 1e-300::float8 - 1e-300")),
0.0
);
assert!(as_f64(&first(&mut e, "SELECT 'nan'::float8 * 2")).is_nan());
}
#[test]
fn float8_out_uses_scientific_notation_like_pg() {
let mut e = Engine::new();
let txt = |e: &mut Engine, sql: &str| -> String {
match e.execute(sql) {
Ok(QueryResult::Rows { rows, .. }) => match &rows[0].values[0] {
spg_storage::Value::Text(s) => s.to_string(),
o => format!("{o:?}"),
},
o => format!("{o:?}"),
}
};
for (expr, want) in [
("1e14", "100000000000000"), ("1e15", "1e+15"), ("1e20", "1e+20"),
("0.0001", "0.0001"), ("0.00001", "1e-05"), ("123456.789", "123456.789"),
("1234567890123456", "1.234567890123456e+15"),
("-2.5e-10", "-2.5e-10"),
("3.14e100", "3.14e+100"),
("1000000.0", "1000000"),
("0.0", "0"),
("-0.0", "0"),
("-0.0::float8", "-0"),
] {
assert_eq!(
txt(&mut e, &format!("SELECT ({expr})::float8::text")),
want,
"float8out({expr})"
);
}
assert_eq!(
txt(
&mut e,
"SELECT (ARRAY[1e30::float8, 2.0::float8, 0.00001::float8])::text"
),
"{1e+30,2,1e-05}"
);
assert_eq!(txt(&mut e, "SELECT ('inf'::float8)::text"), "Infinity");
assert_eq!(txt(&mut e, "SELECT ('-inf'::float8)::text"), "-Infinity");
assert_eq!(txt(&mut e, "SELECT ('nan'::float8)::text"), "NaN");
}