use spg_engine::{Engine, QueryResult};
use spg_storage::Value;
fn rows(e: &mut Engine, sql: &str) -> Vec<Vec<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(|r| r.values).collect()
}
#[test]
fn pg_class_columns_match_pg_canonical() {
let mut e = Engine::new();
let r = e.execute("SELECT * FROM pg_catalog.pg_class").unwrap();
let QueryResult::Rows { columns, .. } = r else {
panic!("expected Rows");
};
let names: Vec<&str> = columns.iter().map(|c| c.name.as_str()).collect();
for must in [
"oid",
"relname",
"relnamespace",
"relkind",
"relnatts",
"relhasindex",
"relhastriggers",
"relispartition",
"reltuples",
"relpages",
"relpersistence",
] {
assert!(
names.contains(&must),
"pg_class missing column {must}, got {names:?}"
);
}
}
#[test]
fn pg_class_relkind_p_for_partition_parent() {
let mut e = Engine::new();
e.execute("CREATE TABLE cust (id BIGINT, region TEXT) PARTITION BY LIST (region)")
.unwrap();
e.execute("CREATE TABLE cust_apac PARTITION OF cust FOR VALUES IN ('jp')")
.unwrap();
e.execute("CREATE TABLE plain (id INT)").unwrap();
let rs = rows(&mut e, "SELECT * FROM pg_catalog.pg_class");
let by_name = |needle: &str| {
rs.iter()
.find(|r| matches!(&r[1], Value::Text(s) if s.as_ref() == needle))
.unwrap_or_else(|| panic!("missing pg_class row for {needle}"))
};
let cust = by_name("cust");
assert!(
matches!(&cust[17], Value::Text(s) if s.as_ref() == "p"),
"parent relkind"
);
assert!(
matches!(cust[27], Value::Bool(false)),
"parent is not a partition"
);
let apac = by_name("cust_apac");
assert!(matches!(apac[27], Value::Bool(true)), "apac is a partition");
let plain = by_name("plain");
assert!(
matches!(&plain[17], Value::Text(s) if s.as_ref() == "r"),
"plain table relkind"
);
assert!(
matches!(plain[27], Value::Bool(false)),
"plain not a partition"
);
}
#[test]
fn pg_class_reltuples_reflects_row_count() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT)").unwrap();
for i in 0..10 {
let sql = format!("INSERT INTO t VALUES ({i})");
e.execute(&sql).unwrap();
}
let rs = rows(&mut e, "SELECT * FROM pg_catalog.pg_class");
let t_row = rs
.iter()
.find(|r| matches!(&r[1], Value::Text(s) if s.as_ref() == "t"))
.expect("missing pg_class row for t");
match t_row[10] {
Value::Float(n) => assert!((n - 10.0).abs() < f64::EPSILON, "reltuples = 10"),
ref other => panic!("expected Float, got {other:?}"),
}
}
#[test]
fn pg_class_relhasindex_after_create_index() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT, name TEXT)").unwrap();
let r1 = rows(&mut e, "SELECT * FROM pg_catalog.pg_class");
let t1 = r1
.iter()
.find(|r| matches!(&r[1], Value::Text(s) if s.as_ref() == "t"))
.unwrap();
assert!(matches!(t1[14], Value::Bool(false)), "no index yet");
e.execute("CREATE INDEX ix_t_name ON t(name)").unwrap();
let r2 = rows(&mut e, "SELECT * FROM pg_catalog.pg_class");
let t2 = r2
.iter()
.find(|r| matches!(&r[1], Value::Text(s) if s.as_ref() == "t"))
.unwrap();
assert!(matches!(t2[14], Value::Bool(true)), "has index");
}
#[test]
fn catalog_view_inside_subquery_materialises() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT NOT NULL)")
.unwrap();
let cols = rows(
&mut e,
"SELECT attname FROM pg_attribute \
WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 't') \
AND attnum > 0 ORDER BY attnum",
);
let names: Vec<String> = cols
.iter()
.filter_map(|r| match &r[0] {
Value::Text(s) => Some(s.to_string()),
_ => None,
})
.collect();
assert_eq!(names, ["id", "name"]);
let idx = rows(
&mut e,
"SELECT indexrelid FROM pg_index \
WHERE indrelid = (SELECT oid FROM pg_class WHERE relname = 't')",
);
assert!(!idx.is_empty(), "expected the primary-key index row");
}