use spg_engine::{Engine, QueryResult};
fn rows(e: &mut Engine, sql: &str) -> Vec<Vec<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.into_iter()
.map(|row| row.values.into_iter().collect())
.collect()
}
fn as_i64(v: &spg_storage::Value<'_>) -> i64 {
match v {
spg_storage::Value::Int(n) => i64::from(*n),
spg_storage::Value::BigInt(n) => *n,
other => panic!("expected integer, got {other:?}"),
}
}
#[test]
fn values_with_column_aliases() {
let mut e = Engine::new();
let got = rows(
&mut e,
"SELECT id, name FROM (VALUES (1, 'a'), (2, 'b')) AS v(id, name) \
ORDER BY id",
);
assert_eq!(got.len(), 2);
assert_eq!(as_i64(&got[0][0]), 1);
assert!(matches!(&got[0][1], spg_storage::Value::Text(s) if s == "a"));
assert_eq!(as_i64(&got[1][0]), 2);
assert!(matches!(&got[1][1], spg_storage::Value::Text(s) if s == "b"));
}
#[test]
fn default_column_names_are_pg_columnn() {
let mut e = Engine::new();
let got = rows(&mut e, "SELECT column2 FROM (VALUES (1, 'x')) t");
assert_eq!(got.len(), 1);
assert!(matches!(&got[0][0], spg_storage::Value::Text(s) if s == "x"));
}
#[test]
fn values_join_and_aggregate() {
let mut e = Engine::new();
e.execute("CREATE TABLE facts (k INT, v TEXT)").unwrap();
e.execute("INSERT INTO facts VALUES (1, 'one'), (2, 'two'), (3, 'three')")
.unwrap();
let got = rows(
&mut e,
"SELECT f.v FROM (VALUES (1), (3)) want(k) \
JOIN facts f ON f.k = want.k ORDER BY f.k",
);
assert_eq!(got.len(), 2);
assert!(matches!(&got[0][0], spg_storage::Value::Text(s) if s == "one"));
assert!(matches!(&got[1][0], spg_storage::Value::Text(s) if s == "three"));
let got = rows(&mut e, "SELECT SUM(x) FROM (VALUES (1), (2), (3)) t(x)");
assert_eq!(as_i64(&got[0][0]), 6);
}
#[test]
fn values_mixed_family_column_unifies_like_pg() {
let mut e = Engine::new();
let r = e
.execute(
"SELECT count(DISTINCT x) FROM (VALUES ('NaN'::float8),(1.0),('NaN'),(2.0),('-0'::float8),(0.0)) t(x)",
)
.unwrap();
let QueryResult::Rows { rows, .. } = r else {
panic!("rows")
};
assert_eq!(
rows[0].values[0],
spg_storage::Value::BigInt(4),
"PG18: column resolves to float8; NaN=NaN, -0=0 → 4 distinct"
);
let r = e
.execute("SELECT count(*) FROM (VALUES ('1'::float8),(1.0),(1)) t(x) WHERE x = 1.0")
.unwrap();
let QueryResult::Rows { rows, .. } = r else {
panic!("rows")
};
assert_eq!(rows[0].values[0], spg_storage::Value::BigInt(3));
}
#[test]
fn real_orders_and_dedups_like_pg() {
let mut e = Engine::new();
let got = rows(
&mut e,
"SELECT x::text FROM (VALUES ('NaN'::float4),(1.5::float4),('Infinity'::float4),\
('-Infinity'::float4),('-0'::float4),(0.0::float4)) t(x) ORDER BY x",
);
let texts: Vec<&str> = got
.iter()
.map(|r| match &r[0] {
spg_storage::Value::Text(s) => s.as_ref(),
other => panic!("expected text, got {other:?}"),
})
.collect();
assert_eq!(
texts,
["-0", "-Infinity", "0", "1.5", "Infinity", "NaN"],
"PG output-column text order"
);
let got = rows(
&mut e,
"SELECT count(DISTINCT x) FROM (VALUES ('NaN'::float4),('NaN'::float4),\
('-0'::float4),(0.0::float4)) t(x)",
);
assert_eq!(got[0][0], spg_storage::Value::BigInt(2));
let got = rows(&mut e, "SELECT 1.5::float4 = 1.5::float8, 2 = 2.0::float4");
assert_eq!(got[0][0], spg_storage::Value::Bool(true));
assert_eq!(got[0][1], spg_storage::Value::Bool(true));
}
#[test]
fn null_branch_column_adopts_concrete_type() {
let mut e = Engine::new();
let got = rows(
&mut e,
"SELECT pg_typeof(x) FROM (VALUES (NULL),(1.5)) t(x) LIMIT 1",
);
assert_eq!(got[0][0], spg_storage::Value::text("numeric"));
let got = rows(&mut e, "SELECT pg_typeof(NULL)");
assert_eq!(got[0][0], spg_storage::Value::text("unknown"));
}