use kimberlite::{Kimberlite, TenantId, Value};
fn open_with_patients() -> (tempfile::TempDir, Kimberlite, kimberlite::TenantHandle) {
let dir = tempfile::tempdir().expect("tempdir");
let db = Kimberlite::open(dir.path()).expect("open db");
let tenant = db.tenant(TenantId::new(42));
tenant
.execute(
"CREATE TABLE patients (id BIGINT PRIMARY KEY, name TEXT NOT NULL)",
&[],
)
.expect("create patients");
(dir, db, tenant)
}
#[test]
fn alter_table_add_column_makes_new_column_queryable() {
let (_dir, _db, tenant) = open_with_patients();
tenant
.execute(
"INSERT INTO patients (id, name) VALUES ($1, $2)",
&[Value::BigInt(1), Value::Text("Alice".into())],
)
.expect("insert pre-alter row");
tenant
.execute("ALTER TABLE patients ADD COLUMN email TEXT", &[])
.expect("ALTER TABLE ADD COLUMN must succeed post-v0.5.0");
tenant
.execute(
"INSERT INTO patients (id, name, email) VALUES ($1, $2, $3)",
&[
Value::BigInt(2),
Value::Text("Bob".into()),
Value::Text("bob@example.com".into()),
],
)
.expect("post-alter insert with new column");
let result = tenant
.query("SELECT id, name, email FROM patients ORDER BY id", &[])
.expect("post-alter select");
assert_eq!(result.rows.len(), 2, "both rows must be visible");
let bob_email = &result.rows[1][2];
assert_eq!(*bob_email, Value::Text("bob@example.com".into()));
let alice_email = &result.rows[0][2];
assert!(
matches!(alice_email, Value::Null),
"pre-alter rows must project NULL for the new column, got {alice_email:?}",
);
}
#[test]
fn alter_table_drop_column_removes_it_from_queries() {
let (_dir, _db, tenant) = open_with_patients();
tenant
.execute("ALTER TABLE patients ADD COLUMN tmp TEXT", &[])
.expect("ADD COLUMN");
tenant
.execute(
"INSERT INTO patients (id, name, tmp) VALUES ($1, $2, $3)",
&[
Value::BigInt(1),
Value::Text("Alice".into()),
Value::Text("will-be-dropped".into()),
],
)
.expect("insert with tmp column");
tenant
.execute("ALTER TABLE patients DROP COLUMN tmp", &[])
.expect("DROP COLUMN must succeed");
let result = tenant
.query("SELECT id, name FROM patients", &[])
.expect("post-drop select");
assert_eq!(result.rows.len(), 1);
assert_eq!(result.rows[0][0], Value::BigInt(1));
assert_eq!(result.rows[0][1], Value::Text("Alice".into()));
let err = tenant
.query("SELECT tmp FROM patients", &[])
.expect_err("dropped column must not be queryable");
let err_str = format!("{err}");
assert!(
err_str.contains("tmp") || err_str.to_lowercase().contains("column"),
"error should mention the missing column, got: {err_str}",
);
}
#[test]
fn alter_table_cannot_drop_primary_key_column() {
let (_dir, _db, tenant) = open_with_patients();
let err = tenant
.execute("ALTER TABLE patients DROP COLUMN id", &[])
.expect_err("dropping a PK column must be rejected");
let err_str = format!("{err}");
assert!(
err_str.to_lowercase().contains("primary") || err_str.contains("id"),
"error must mention primary key or column name, got: {err_str}",
);
}
#[test]
fn alter_table_add_duplicate_column_is_rejected() {
let (_dir, _db, tenant) = open_with_patients();
let err = tenant
.execute("ALTER TABLE patients ADD COLUMN name TEXT", &[])
.expect_err("duplicate ADD COLUMN must be rejected");
let err_str = format!("{err}");
assert!(
err_str.to_lowercase().contains("already") || err_str.contains("name"),
"error must mention duplicate / column name, got: {err_str}",
);
}