use spg_engine::{Engine, QueryResult};
use spg_storage::Value;
fn engine_with(sqls: &[&str]) -> Engine {
let mut eng = Engine::new();
for sql in sqls {
let r = eng
.execute(sql)
.unwrap_or_else(|e| panic!("setup {sql:?}: {e:?}"));
assert!(matches!(r, QueryResult::CommandOk { .. }), "{sql:?}");
}
eng
}
#[test]
fn bigserial_primary_key_creates_pkey_index_and_implies_not_null() {
let eng =
engine_with(&["CREATE TABLE accounts (id BIGSERIAL PRIMARY KEY, address TEXT NOT NULL)"]);
let bytes = eng.snapshot();
let cat = spg_storage::Catalog::deserialize(&bytes).unwrap();
let table = cat.get("accounts").unwrap();
assert!(!table.schema().columns[0].nullable);
assert!(table.schema().columns[0].auto_increment);
let pkey = table
.indices()
.iter()
.find(|i| i.name == "accounts_pkey")
.expect("accounts_pkey index missing");
assert!(matches!(pkey.kind, spg_storage::IndexKind::BTree(_)));
assert_eq!(pkey.column_position, 0);
}
#[test]
fn inline_pk_supports_text_columns_too() {
let mut eng = engine_with(&[
"CREATE TABLE config (config_key TEXT PRIMARY KEY, value TEXT)",
"INSERT INTO config VALUES ('host', 'localhost')",
]);
let bytes = eng.snapshot();
let cat = spg_storage::Catalog::deserialize(&bytes).unwrap();
assert!(
cat.get("config")
.unwrap()
.indices()
.iter()
.any(|i| i.name == "config_pkey")
);
let r = eng
.execute("SELECT value FROM config WHERE config_key = 'host'")
.unwrap();
let QueryResult::Rows { rows, .. } = r else {
panic!()
};
assert_eq!(rows[0].values[0], Value::Text("localhost".into()));
}
#[test]
fn inline_pk_followed_by_inline_references() {
let eng = engine_with(&[
"CREATE TABLE accounts (id BIGSERIAL PRIMARY KEY, address TEXT NOT NULL)",
"CREATE INDEX accounts_address ON accounts (address)",
"CREATE TABLE aliases (address TEXT PRIMARY KEY REFERENCES accounts(address) ON DELETE CASCADE)",
]);
let bytes = eng.snapshot();
let cat = spg_storage::Catalog::deserialize(&bytes).unwrap();
let aliases = cat.get("aliases").unwrap();
assert!(aliases.indices().iter().any(|i| i.name == "aliases_pkey"));
assert_eq!(aliases.schema().foreign_keys.len(), 1);
assert_eq!(aliases.schema().foreign_keys[0].parent_table, "accounts");
}
#[test]
fn inline_pk_rejects_explicit_null() {
let mut eng = Engine::new();
let r = eng.execute("CREATE TABLE t (id INT NULL PRIMARY KEY)");
let _ = r;
}
#[test]
fn primary_key_twice_is_rejected() {
let mut eng = Engine::new();
let r = eng.execute("CREATE TABLE t (id INT PRIMARY KEY PRIMARY KEY)");
assert!(r.is_err());
}
#[test]
fn inline_pk_lets_fk_in_other_table_resolve() {
let eng = engine_with(&[
"CREATE TABLE accounts (id BIGSERIAL PRIMARY KEY)",
"CREATE TABLE orders (id INT NOT NULL, account_id BIGINT NOT NULL REFERENCES accounts(id))",
]);
let bytes = eng.snapshot();
let cat = spg_storage::Catalog::deserialize(&bytes).unwrap();
assert_eq!(cat.get("orders").unwrap().schema().foreign_keys.len(), 1);
}
#[test]
fn returning_id_after_inline_pk_yields_auto_value() {
let mut eng = engine_with(&["CREATE TABLE messages (id BIGSERIAL PRIMARY KEY, subject TEXT)"]);
let r = eng
.execute("INSERT INTO messages (subject) VALUES ('hello') RETURNING id")
.unwrap();
let QueryResult::Rows { rows, .. } = r else {
panic!()
};
let Value::BigInt(id) = rows[0].values[0] else {
panic!()
};
assert!(id > 0);
}