use spg_engine::{Engine, QueryResult};
use spg_storage::Value;
fn rows_of(r: &QueryResult) -> Vec<String> {
match r {
QueryResult::Rows { rows, .. } => rows
.iter()
.filter_map(|row| {
if let Value::Text(s) = &row.values[0] {
Some(s.to_string())
} else {
None
}
})
.collect(),
_ => Vec::new(),
}
}
#[test]
fn every_operator_reports_stats() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
.unwrap();
for i in 0..10 {
e.execute(&format!("INSERT INTO t VALUES ({i}, 'n{i}')"))
.unwrap();
}
let r = e.execute("EXPLAIN ANALYZE SELECT * FROM t").unwrap();
let lines = rows_of(&r);
assert!(!lines.is_empty(), "EXPLAIN ANALYZE must emit ≥ 1 line");
assert!(
lines[0].contains("(actual ") && lines[0].contains("loops=1"),
"top node carries the measured block: {:?}",
lines[0]
);
assert!(
!lines[0].contains("actual time="),
"no clock ⇒ no per-node time: {:?}",
lines[0]
);
}
#[test]
fn top_level_rows_match_result_count() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
for i in 0..7 {
e.execute(&format!("INSERT INTO t VALUES ({i})")).unwrap();
}
let r = e.execute("EXPLAIN ANALYZE SELECT * FROM t").unwrap();
let lines = rows_of(&r);
let top = &lines[0];
assert!(
top.contains("rows=7.00 loops=1"),
"top reports actual result rows; got {top:?}"
);
assert!(
!lines.iter().any(|l| l.starts_with("Execution Time: ")),
"no clock ⇒ no fabricated timing line: {lines:?}"
);
}
#[test]
fn scan_reports_catalog_row_count() {
let mut e = Engine::new();
e.execute("CREATE TABLE big (id INT NOT NULL)").unwrap();
for i in 0..40 {
e.execute(&format!("INSERT INTO big VALUES ({i})")).unwrap();
}
let r = e
.execute("EXPLAIN ANALYZE SELECT * FROM big WHERE id < 10")
.unwrap();
let lines = rows_of(&r);
let from_line = lines
.iter()
.find(|l| l.contains("Seq Scan on big"))
.expect("scan line present");
assert!(
from_line.contains("rows=10.00 loops=1"),
"scan reports actual output rows; got {from_line:?}"
);
assert!(
lines
.iter()
.any(|l| l.contains("Rows Removed by Filter: 30")),
"PG's removed-rows line: {lines:?}"
);
}
#[test]
fn no_unknown_operator_in_top_level() {
let known: [&str; 8] = [
"Seq",
"Index",
"Result",
"Aggregate",
"HashAggregate",
"WindowAgg",
"Append",
"CTE",
];
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
for i in 0..5 {
e.execute(&format!("INSERT INTO t VALUES ({i})")).unwrap();
}
let queries = [
"EXPLAIN ANALYZE SELECT * FROM t",
"EXPLAIN ANALYZE SELECT count(*) FROM t",
"EXPLAIN ANALYZE SELECT DISTINCT id FROM t",
"EXPLAIN ANALYZE SELECT 1",
"EXPLAIN ANALYZE SELECT * FROM t UNION SELECT * FROM t",
];
for q in queries {
let r = e.execute(q).unwrap();
let lines = rows_of(&r);
let top = &lines[0];
let stripped = top.split_once(' ').map_or(top.as_str(), |(head, _)| head);
assert!(
known.iter().any(|k| stripped.starts_with(k)),
"unknown top operator {stripped:?} for query {q:?}"
);
}
}
#[test]
fn scan_omits_cold_marker_when_no_cold_segments() {
let mut e = Engine::new();
e.execute("CREATE TABLE warm (id INT NOT NULL)").unwrap();
e.execute("INSERT INTO warm VALUES (1)").unwrap();
let r = e.execute("EXPLAIN ANALYZE SELECT * FROM warm").unwrap();
let lines = rows_of(&r);
let from = lines
.iter()
.find(|l| l.contains("Seq Scan on warm"))
.unwrap();
assert!(
from.contains("rows=1.00 loops=1"),
"scan line shows actual rows; got {from:?}"
);
assert!(
!from.contains("cold_tier"),
"no cold marker on the PG-shaped node line; got {from:?}"
);
}
#[test]
fn execution_time_line_present_when_clock_is_set() {
let mut e = Engine::new().with_clock(|| 1_000_000);
e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
e.execute("INSERT INTO t VALUES (1)").unwrap();
let r = e.execute("EXPLAIN ANALYZE SELECT * FROM t").unwrap();
let lines = rows_of(&r);
let total = lines
.iter()
.find(|l| l.starts_with("Execution Time: "))
.expect("Execution Time line present");
assert!(total.ends_with(" ms"), "PG unit suffix: {total:?}");
}