use spg_engine::Engine;
use spg_storage::Catalog;
#[test]
fn create_index_with_include_persists_columns() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL, age INT NOT NULL)")
.unwrap();
e.execute("CREATE INDEX by_id ON t (id) INCLUDE (name, age)")
.unwrap();
let t = e.catalog().get("t").expect("table");
let idx = t
.indices()
.iter()
.find(|i| i.name == "by_id")
.expect("index by_id");
assert_eq!(idx.included_columns, vec![1, 2]);
}
#[test]
fn create_index_without_include_has_empty_vec() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
.unwrap();
e.execute("CREATE INDEX by_id ON t (id)").unwrap();
let t = e.catalog().get("t").expect("table");
let idx = t
.indices()
.iter()
.find(|i| i.name == "by_id")
.expect("index by_id");
assert!(idx.included_columns.is_empty());
}
#[test]
fn create_index_include_unknown_column_errors() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
.unwrap();
let r = e.execute("CREATE INDEX by_id ON t (id) INCLUDE (name, ghost)");
assert!(
r.is_err(),
"unknown INCLUDE column must error before catalog mutation lands"
);
let t = e.catalog().get("t").expect("table");
assert!(t.indices().iter().all(|i| i.name != "by_id"));
}
#[test]
fn create_index_include_rejected_on_hnsw() {
let mut e = Engine::new();
e.execute("CREATE TABLE emb (id INT NOT NULL, v VECTOR(4) NOT NULL, tag TEXT)")
.unwrap();
let r = e.execute("CREATE INDEX emb_idx ON emb USING hnsw (v) INCLUDE (tag)");
assert!(
r.is_err(),
"INCLUDE on HNSW must error — included data is only meaningful for BTree covered scans"
);
}
#[test]
fn included_columns_survive_catalog_snapshot_roundtrip() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL, age INT NOT NULL)")
.unwrap();
e.execute("CREATE INDEX by_id ON t (id) INCLUDE (name, age)")
.unwrap();
let bytes = e.catalog().serialize();
let restored = Catalog::deserialize(&bytes).expect("deserialize");
let idx = restored
.get("t")
.unwrap()
.indices()
.iter()
.find(|i| i.name == "by_id")
.expect("index by_id");
assert_eq!(idx.included_columns, vec![1, 2]);
}
#[test]
fn legacy_v11_snapshot_loads_with_empty_included() {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
.unwrap();
e.execute("CREATE INDEX by_id ON t (id)").unwrap();
let bytes = e.catalog().serialize();
let restored = Catalog::deserialize(&bytes).expect("deserialize");
let idx = restored
.get("t")
.unwrap()
.indices()
.iter()
.find(|i| i.name == "by_id")
.unwrap();
assert!(idx.included_columns.is_empty());
}
#[test]
fn create_index_display_round_trips_include() {
use spg_sql::ast::Statement;
use spg_sql::parser::parse_statement;
let sql = "CREATE INDEX by_id ON t (id) INCLUDE (name, age)";
let stmt = parse_statement(sql).unwrap();
let Statement::CreateIndex(ref s) = stmt else {
panic!("expected CreateIndex");
};
assert_eq!(
s.included_columns,
vec!["name".to_string(), "age".to_string()]
);
let s2 = parse_statement(&stmt.to_string()).unwrap();
assert_eq!(s2, stmt);
}