use powdb_query::executor::Engine;
use powdb_query::result::QueryResult;
use powdb_storage::types::Value;
fn one_int(result: QueryResult) -> i64 {
match result {
QueryResult::Rows { rows, .. } => match rows[0][0] {
Value::Int(n) => n,
ref v => panic!("expected int, got {v:?}"),
},
other => panic!("expected rows, got {other:?}"),
}
}
#[test]
fn powql_auto_assigns_sequential_ids_and_persists() {
let dir = tempfile::tempdir().unwrap();
{
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type T { unique auto id: int, required name: str }")
.unwrap();
let id1 = one_int(
engine
.execute_powql(r#"insert T { name := "a" } returning"#)
.unwrap(),
);
let id2 = one_int(
engine
.execute_powql(r#"insert T { name := "b" } returning"#)
.unwrap(),
);
assert_eq!(id1, 1, "first auto id");
assert_eq!(id2, 2, "second auto id");
}
let mut engine = Engine::new(dir.path()).unwrap();
let id3 = one_int(
engine
.execute_powql(r#"insert T { name := "c" } returning"#)
.unwrap(),
);
assert_eq!(id3, 3, "sequence resumes after restart");
match engine.execute_powql("T order .id { .id, .name }").unwrap() {
QueryResult::Rows { rows, .. } => {
assert_eq!(rows.len(), 3);
assert_eq!(rows[0], vec![Value::Int(1), Value::Str("a".into())]);
assert_eq!(rows[1], vec![Value::Int(2), Value::Str("b".into())]);
assert_eq!(rows[2], vec![Value::Int(3), Value::Str("c".into())]);
}
other => panic!("expected rows, got {other:?}"),
}
}
#[test]
fn auto_sequence_recovers_after_crash() {
let dir = tempfile::tempdir().unwrap();
{
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type T { unique auto id: int, required n: str }")
.unwrap();
engine
.execute_powql(r#"insert T { n := "a" }, { n := "b" }"#)
.unwrap();
std::mem::forget(engine); }
let mut engine = Engine::new(dir.path()).unwrap();
let id = one_int(
engine
.execute_powql(r#"insert T { n := "c" } returning"#)
.unwrap(),
);
assert_eq!(id, 3, "sequence resumes above the WAL-replayed rows");
}
#[test]
fn sql_autoincrement_assigns_ids() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_sql("CREATE TABLE T (id INTEGER UNIQUE AUTOINCREMENT, name TEXT NOT NULL)")
.unwrap();
engine
.execute_sql("INSERT INTO T (name) VALUES ('a')")
.unwrap();
engine
.execute_sql("INSERT INTO T (name) VALUES ('b')")
.unwrap();
match engine.execute_sql("SELECT id FROM T ORDER BY id").unwrap() {
QueryResult::Rows { rows, .. } => {
assert_eq!(rows[0][0], Value::Int(1));
assert_eq!(rows[1][0], Value::Int(2));
}
other => panic!("expected rows, got {other:?}"),
}
}
#[test]
fn multi_row_insert_assigns_distinct_ids() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type T { unique auto id: int, required n: str }")
.unwrap();
engine
.execute_powql(r#"insert T { n := "a" }, { n := "b" }, { n := "c" }"#)
.unwrap();
match engine.execute_powql("T order .id { .id }").unwrap() {
QueryResult::Rows { rows, .. } => {
assert_eq!(rows.len(), 3);
assert_eq!(rows[0][0], Value::Int(1));
assert_eq!(rows[1][0], Value::Int(2));
assert_eq!(rows[2][0], Value::Int(3));
}
other => panic!("expected rows, got {other:?}"),
}
}
#[test]
fn explicit_value_advances_the_sequence() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type T { unique auto id: int, required n: str }")
.unwrap();
engine
.execute_powql(r#"insert T { id := 100, n := "explicit" }"#)
.unwrap();
let next = one_int(
engine
.execute_powql(r#"insert T { n := "auto" } returning"#)
.unwrap(),
);
assert_eq!(next, 101, "sequence resumes above the explicit value");
}
#[test]
fn auto_on_non_int_column_is_rejected() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
let err = engine
.execute_powql("type T { auto name: str }")
.unwrap_err();
assert!(
err.to_string().to_lowercase().contains("int"),
"auto-on-str should mention the int requirement, got: {err}"
);
}
#[test]
fn double_rollback_of_reused_auto_id_does_not_poison_index() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type D { unique auto id: int, required v: int }")
.unwrap();
engine.execute_powql("insert D { v := 1 }").unwrap();
engine.execute_powql("begin").unwrap();
engine.execute_powql("insert D { v := 2 }").unwrap();
engine.execute_powql("rollback").unwrap();
engine.execute_powql("insert D { v := 3 }").unwrap();
engine.execute_powql("begin").unwrap();
engine.execute_powql("insert D { v := 4 }").unwrap();
engine.execute_powql("rollback").unwrap();
engine.execute_powql("begin").unwrap();
engine.execute_powql("insert D { v := 5 }").unwrap();
engine.execute_powql("rollback").unwrap();
engine
.execute_powql("insert D { v := 6 }")
.expect("reusing rolled-back auto id 3 must not hit a phantom unique violation");
match engine.execute_powql("D order .id { .id, .v }").unwrap() {
QueryResult::Rows { rows, .. } => {
let got: Vec<(i64, i64)> = rows
.iter()
.map(|r| match (&r[0], &r[1]) {
(Value::Int(id), Value::Int(v)) => (*id, *v),
other => panic!("expected (int,int), got {other:?}"),
})
.collect();
assert_eq!(
got,
vec![(1, 1), (2, 3), (3, 6)],
"unexpected surviving rows"
);
}
other => panic!("expected rows, got {other:?}"),
}
let looked_up = one_int(engine.execute_powql("D filter .id = 3 { .v }").unwrap());
assert_eq!(looked_up, 6, "index lookup on .id returned the wrong row");
}
#[test]
fn double_rollback_poison_absent_after_reopen() {
let dir = tempfile::tempdir().unwrap();
{
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type D { unique auto id: int, required v: int }")
.unwrap();
engine.execute_powql("insert D { v := 1 }").unwrap();
engine.execute_powql("begin").unwrap();
engine.execute_powql("insert D { v := 2 }").unwrap();
engine.execute_powql("rollback").unwrap();
engine.execute_powql("insert D { v := 3 }").unwrap();
engine.execute_powql("begin").unwrap();
engine.execute_powql("insert D { v := 4 }").unwrap();
engine.execute_powql("rollback").unwrap();
engine.execute_powql("begin").unwrap();
engine.execute_powql("insert D { v := 5 }").unwrap();
engine.execute_powql("rollback").unwrap();
}
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("insert D { v := 6 }")
.expect("phantom unique entry survived reopen");
let looked_up = one_int(engine.execute_powql("D filter .id = 3 { .v }").unwrap());
assert_eq!(
looked_up, 6,
"index lookup after reopen returned the wrong row"
);
}
#[test]
fn single_rollback_cleanly_removes_index_entry() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type D { unique auto id: int, required v: int }")
.unwrap();
engine.execute_powql("insert D { v := 1 }").unwrap();
engine.execute_powql("begin").unwrap();
engine.execute_powql("insert D { v := 2 }").unwrap();
engine.execute_powql("rollback").unwrap();
engine
.execute_powql("insert D { v := 3 }")
.expect("single rollback left a phantom index entry");
let looked_up = one_int(engine.execute_powql("D filter .id = 2 { .v }").unwrap());
assert_eq!(looked_up, 3);
}
#[test]
fn auto_with_default_is_rejected() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
let err = engine
.execute_powql("type T { auto id: int default 5 }")
.unwrap_err();
assert!(
err.to_string().to_lowercase().contains("default"),
"auto+default should be rejected, got: {err}"
);
}